Trabajo de fin de máster IA


Comparativa de soluciones utilizando series temporales para la predicción de glucosa en diabetes tipo 1 del experimento de 110 pacientes

En este estudio se va a utilizar la metodología del Descubrimiento de Conocimiento en Bases de Datos (KDD) con el objetivo de descubrir patrones, relaciones y tendencias ocultas en los datos, con el fin de tomar decisiones informadas y predecir la glucosa en los 15 minutos siguientes de los pacientes, y finalmente realizar la comparativa de soluciones con los algoritmos utilizados, las redes neuronales recurrentes (RNN) y redes neuronales recurrentes convolucionales (CRNN).

La metodlogía KDD se compone de cinco fases principales de manera iterativa (se juntan Preprocesamiento y Transformación):

  1. Selección.
  2. Preprocesamiento y Transformación.
  3. Minería de datos.
  4. Evaluación e implantación.

1. Selección¶

Se ha seleccionado para este estudio de 110 pacientes y 4 columnas: "Patient_ID", "Measurement_date", "Measurement_time", "Measurement"

Leer dataframe y mostrar¶

In [1]:
# Librerías
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import sklearn as sklearn
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import r2_score
from sklearn.tree import DecisionTreeRegressor,DecisionTreeClassifier
from sklearn.ensemble import RandomForestRegressor,RandomForestClassifier
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import cross_val_score
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import KFold
from IPython.display import display, HTML

df = pd.read_csv("Glucose_measurements_sample.csv")
df
Out[1]:
Patient_ID Measurement_date Measurement_time Measurement
0 LIB193263 2020-06-09 19:08:00 99
1 LIB193263 2020-06-09 19:23:00 92
2 LIB193263 2020-06-09 19:38:00 86
3 LIB193263 2020-06-09 19:53:00 85
4 LIB193263 2020-06-09 20:08:00 85
... ... ... ... ...
2999995 LIB193424 2022-01-02 01:05:00 207
2999996 LIB193424 2022-01-02 01:20:00 215
2999997 LIB193424 2022-01-02 01:35:00 218
2999998 LIB193424 2022-01-02 01:50:00 222
2999999 LIB193424 2022-01-02 02:05:00 220

3000000 rows × 4 columns

2. Preprocesamiento y Transformación de datos¶

El preprocesamiento y la transformación de datos son dos fases críticas. Estas fases se dividen en varias tareas:

El preprocesamiento de los datos:

  • Limpieza de los datos: Se eliminan datos incompletos, pero no hay. Lo que se ha hecho es la transformación de los datos.
  • Transformación de los datos: Se transforman los datos en formato adecuado para el análisis.
  • Selección de características: Se selecciona las 4 columnas, los pacientes, fecha, hora de medición y glucosa.

La Transformación de los datos:

  • Extracción de características: Se crean nuevas características a partir de las características existentes. Es decir, se extrae información del conjunto de datos para crear nuevas variables o columnas a partir de las existentes. De esta manera una vez creadas las nuevas características, se pueden utilizar junto con las características originales para entrenar y evaluar el modelo de aprendizaje automático, pudiendo quizá mejorar el rendimiento del modelo.
  • Normalización de datos: Se transforman los datos para que tengan una distribución normal o uniforme, lo que facilita el análisis.

Resumen estadístico de la glucosa (Measurement)¶

Estadística como la media, el mínimo y el máximo, la desviación estándar y los percentiles.

count: es la cantidad de observaciones (mediciones) en los datos. En este caso, hay 3 millones de observaciones.

mean: es la media aritmética de todas las observaciones. En este caso, el valor medio de las mediciones es de 158.95.

std: es la desviación estándar, que mide la cantidad de variación o dispersión que hay en los datos. Una desviación estándar mayor indica que los datos están más dispersos y tienen más variabilidad. En este caso, la desviación estándar es de 65.43.

min: es el valor mínimo en los datos. En este caso, el valor más bajo en las mediciones es 40. Se debe a que el freestlye libre 2 no responde a niveles de glucosa por debajo de 40 mg/dl.

25%: es el primer cuartil, es decir, el valor que es mayor que el 25% de las observaciones. En este caso, el 25% de las mediciones tienen un valor de 110 o menos.

50%: es el segundo cuartil, es decir, el valor que es mayor que el 50% de las observaciones (también conocido como la mediana). En este caso, la mediana de las mediciones es 148.

75%: es el tercer cuartil, es decir, el valor que es mayor que el 75% de las observaciones. En este caso, el 75% de las mediciones tienen un valor de 197 o menos.

max: es el valor máximo en los datos. En este caso, el valor más alto en las mediciones es 500. Se debe a que el freestlye libre 2 no responde a niveles de glucosa por encima de 500 mg/dl.

Los valores de los percentiles (25%, 50% y 75%) proporcionan cómo se distribuyen los datos alrededor de la mediana. En este caso, el hecho de que el tercer cuartil sea 197 significa que el 25% de las mediciones tienen un valor entre 148 y 197.

In [2]:
# Mostrar estadística, como extremos atípicos y grandes desviaciones
df.describe().round(2)
Out[2]:
Measurement
count 3000000.00
mean 158.95
std 65.43
min 40.00
25% 110.00
50% 148.00
75% 197.00
max 500.00

Mostrar los tipos de datos del dataframe¶

Los valores de los datos (string/object, float, int, boolean) son importantes para el manejo de los datos.

Para un mejor manejo de los datos con Pandas, es recomendable que la columna "Patient_ID" contenga valores enteros en lugar de cadenas de texto. Además, para poder aprovechar al máximo las funcionalidades de Pandas, es conveniente que las columnas "Measurement_date" y "Measurement_time" se conviertan a formato de fecha y hora (datetime).

In [3]:
# Los tipos de valores de los datos son float o enteros
df.dtypes
Out[3]:
Patient_ID          object
Measurement_date    object
Measurement_time    object
Measurement          int64
dtype: object

**Limpieza de datos y Transformación de los datos**

La limpieza de los datos es una parte crucial del proceso de obtención, ya que permite adecuar el contenido a nuestras necesidades y hacerlos más fáciles de utilizar. Una forma de mejorar la eficiencia del uso de los datos es renombrar los valores de la columna "Patient_ID" y de las columnas "Measurement_date" y "Measurement_time" para simplificar su uso en pandas y reducir su espacio en pantalla.

Comprobar si hay valores nulos y faltantes¶

In [4]:
# Verificar si hay valores nulos
if df.isnull().any().any():
    print("Hay valores nulos en el dataframe")
else:
    print("No hay valores nulos en el dataframe")

# Verificar si hay valores faltantes
if df.isna().any().any():
    print("Hay valores faltantes en el dataframe")
else:
    print("No hay valores faltantes en el dataframe")
No hay valores nulos en el dataframe
No hay valores faltantes en el dataframe

Muestra los pacientes con su índice único¶

In [5]:
# Obtener una lista de valores únicos de la columna Patient_ID del DataFrame df
unique_patient_ids = df['Patient_ID'].unique()

# Iterar sobre la lista de unique_patient_ids para ver el número de cada paciente único
for i, patient_id in enumerate(unique_patient_ids):
    # Imprimir el número del paciente actual (i) y su ID (patient_id)
    print(f'Número para el paciente {patient_id}: {i}')
Número para el paciente LIB193263: 0
Número para el paciente LIB193264: 1
Número para el paciente LIB193265: 2
Número para el paciente LIB193266: 3
Número para el paciente LIB193267: 4
Número para el paciente LIB193268: 5
Número para el paciente LIB193269: 6
Número para el paciente LIB193272: 7
Número para el paciente LIB193273: 8
Número para el paciente LIB193274: 9
Número para el paciente LIB193276: 10
Número para el paciente LIB193277: 11
Número para el paciente LIB193278: 12
Número para el paciente LIB193279: 13
Número para el paciente LIB193280: 14
Número para el paciente LIB193281: 15
Número para el paciente LIB193282: 16
Número para el paciente LIB193283: 17
Número para el paciente LIB193284: 18
Número para el paciente LIB193302: 19
Número para el paciente LIB193303: 20
Número para el paciente LIB193304: 21
Número para el paciente LIB193307: 22
Número para el paciente LIB193308: 23
Número para el paciente LIB193309: 24
Número para el paciente LIB193310: 25
Número para el paciente LIB193311: 26
Número para el paciente LIB193312: 27
Número para el paciente LIB193313: 28
Número para el paciente LIB193314: 29
Número para el paciente LIB193315: 30
Número para el paciente LIB193317: 31
Número para el paciente LIB193318: 32
Número para el paciente LIB193319: 33
Número para el paciente LIB193320: 34
Número para el paciente LIB193324: 35
Número para el paciente LIB193325: 36
Número para el paciente LIB193326: 37
Número para el paciente LIB193328: 38
Número para el paciente LIB193330: 39
Número para el paciente LIB193332: 40
Número para el paciente LIB193333: 41
Número para el paciente LIB193334: 42
Número para el paciente LIB193335: 43
Número para el paciente LIB193337: 44
Número para el paciente LIB193338: 45
Número para el paciente LIB193340: 46
Número para el paciente LIB193341: 47
Número para el paciente LIB193342: 48
Número para el paciente LIB193343: 49
Número para el paciente LIB193344: 50
Número para el paciente LIB193345: 51
Número para el paciente LIB193346: 52
Número para el paciente LIB193347: 53
Número para el paciente LIB193349: 54
Número para el paciente LIB193350: 55
Número para el paciente LIB193351: 56
Número para el paciente LIB193352: 57
Número para el paciente LIB193353: 58
Número para el paciente LIB193354: 59
Número para el paciente LIB193356: 60
Número para el paciente LIB193357: 61
Número para el paciente LIB193358: 62
Número para el paciente LIB193361: 63
Número para el paciente LIB193363: 64
Número para el paciente LIB193365: 65
Número para el paciente LIB193366: 66
Número para el paciente LIB193367: 67
Número para el paciente LIB193368: 68
Número para el paciente LIB193369: 69
Número para el paciente LIB193370: 70
Número para el paciente LIB193371: 71
Número para el paciente LIB193372: 72
Número para el paciente LIB193375: 73
Número para el paciente LIB193376: 74
Número para el paciente LIB193377: 75
Número para el paciente LIB193378: 76
Número para el paciente LIB193379: 77
Número para el paciente LIB193380: 78
Número para el paciente LIB193381: 79
Número para el paciente LIB193382: 80
Número para el paciente LIB193383: 81
Número para el paciente LIB193384: 82
Número para el paciente LIB193385: 83
Número para el paciente LIB193386: 84
Número para el paciente LIB193387: 85
Número para el paciente LIB193389: 86
Número para el paciente LIB193390: 87
Número para el paciente LIB193391: 88
Número para el paciente LIB193392: 89
Número para el paciente LIB193393: 90
Número para el paciente LIB193395: 91
Número para el paciente LIB193397: 92
Número para el paciente LIB193398: 93
Número para el paciente LIB193399: 94
Número para el paciente LIB193400: 95
Número para el paciente LIB193404: 96
Número para el paciente LIB193406: 97
Número para el paciente LIB193407: 98
Número para el paciente LIB193408: 99
Número para el paciente LIB193410: 100
Número para el paciente LIB193411: 101
Número para el paciente LIB193412: 102
Número para el paciente LIB193414: 103
Número para el paciente LIB193416: 104
Número para el paciente LIB193418: 105
Número para el paciente LIB193419: 106
Número para el paciente LIB193420: 107
Número para el paciente LIB193423: 108
Número para el paciente LIB193424: 109

Convertir los pacientes "Patient_ID" de String (Object) a datos enteros (INT)¶

In [6]:
from sklearn.preprocessing import LabelEncoder

# crear una instancia de LabelEncoder()
le = LabelEncoder()

# convertir las columnas a formato numérico utilizando LabelEncoder()
df['Patient_ID'] = le.fit_transform(df['Patient_ID'])

# Los tipos de valores de los datos son float o enteros
df.dtypes
Out[6]:
Patient_ID           int32
Measurement_date    object
Measurement_time    object
Measurement          int64
dtype: object

Convertir los datos "Measurement_date" y "Measurement_time" de String (Object) a formato de fecha y hora (datetime) para poder manejarlo con pandas¶

In [7]:
df['Measurement_date'] = pd.to_datetime(df['Measurement_date'])
df['Measurement_time'] = pd.to_datetime(df['Measurement_time'], format='%H:%M:%S')

df.dtypes
Out[7]:
Patient_ID                   int32
Measurement_date    datetime64[ns]
Measurement_time    datetime64[ns]
Measurement                  int64
dtype: object

**Extracción de característcas**

La extracción de características consiste en conseguir optimizar el entrenamiento del modelo de predicción de niveles de glucosa en sangre. Se agrega varias columnas al conjunto de datos, como "In_Range", "Hyperglycemia", "Hypoglycemia", "Hour_of_Day" y "Day_of_Week", para analizar cómo estas características afectan la precisión del modelo.

  • "In_Range" indica si una medición está dentro del rango de niveles normales.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hour_of_Day" para indicar la hora del día en que se tomó cada medición.
  • "Day_of_Week" para indicar el día de la semana. 0 es lunes y 6 es domingo.

Esta información puede mejorar la precisión del modelo ya que las mediciones que están fuera del rango normal y las mediciones más recientes son más relevantes para predecir los niveles futuros de glucosa, mientras que las características de hora del día y día de la semana pueden ayudar al modelo a capturar patrones diarios y semanales en los niveles de glucosa, lo cual puede mejorar la precisión de las predicciones.

Añadir columna: Tiempo en rango "In_Range"¶

In [8]:
# Crear una nueva columna llamada 'In_Range' que sea True si la medición está dentro del rango y False si no lo está
df['In_Range'] = (df['Measurement'] >= 70) & (df['Measurement'] <= 180)

# Mostrar el resultado
df
Out[8]:
Patient_ID Measurement_date Measurement_time Measurement In_Range
0 0 2020-06-09 1900-01-01 19:08:00 99 True
1 0 2020-06-09 1900-01-01 19:23:00 92 True
2 0 2020-06-09 1900-01-01 19:38:00 86 True
3 0 2020-06-09 1900-01-01 19:53:00 85 True
4 0 2020-06-09 1900-01-01 20:08:00 85 True
... ... ... ... ... ...
2999995 109 2022-01-02 1900-01-01 01:05:00 207 False
2999996 109 2022-01-02 1900-01-01 01:20:00 215 False
2999997 109 2022-01-02 1900-01-01 01:35:00 218 False
2999998 109 2022-01-02 1900-01-01 01:50:00 222 False
2999999 109 2022-01-02 1900-01-01 02:05:00 220 False

3000000 rows × 5 columns

Añadir columna: Fuera de rango "Hypoglycemia" y "Hyperglycemia"¶

In [9]:
# Crear una nueva columna llamada 'Hypoglycemia' que sea True si la medición está por debajo de 70 mg/dl y False si no lo está
df['Hypoglycemia'] = (df['Measurement'] < 70)

# Crear una nueva columna llamada 'Hyperglycemia' que sea True si la medición está por encima de 180 mg/dl y False si no lo está
df['Hyperglycemia'] = (df['Measurement'] > 180)

# Mostrar el resultado
df
Out[9]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia
0 0 2020-06-09 1900-01-01 19:08:00 99 True False False
1 0 2020-06-09 1900-01-01 19:23:00 92 True False False
2 0 2020-06-09 1900-01-01 19:38:00 86 True False False
3 0 2020-06-09 1900-01-01 19:53:00 85 True False False
4 0 2020-06-09 1900-01-01 20:08:00 85 True False False
... ... ... ... ... ... ... ...
2999995 109 2022-01-02 1900-01-01 01:05:00 207 False False True
2999996 109 2022-01-02 1900-01-01 01:20:00 215 False False True
2999997 109 2022-01-02 1900-01-01 01:35:00 218 False False True
2999998 109 2022-01-02 1900-01-01 01:50:00 222 False False True
2999999 109 2022-01-02 1900-01-01 02:05:00 220 False False True

3000000 rows × 7 columns

Añadir columna: Hora del día "Hour_of_Day"¶

In [10]:
# Convertir la columna 'Measurement_time' a formato de hora de Pandas
df['Measurement_time'] = pd.to_datetime(df['Measurement_time'], format='%H:%M:%S').dt.time

# Extraer la hora del día de la columna 'Measurement_time'
df['Hour_of_Day'] = df['Measurement_time'].apply(lambda x: x.hour)

# Mostrar el resultado
df
Out[10]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day
0 0 2020-06-09 19:08:00 99 True False False 19
1 0 2020-06-09 19:23:00 92 True False False 19
2 0 2020-06-09 19:38:00 86 True False False 19
3 0 2020-06-09 19:53:00 85 True False False 19
4 0 2020-06-09 20:08:00 85 True False False 20
... ... ... ... ... ... ... ... ...
2999995 109 2022-01-02 01:05:00 207 False False True 1
2999996 109 2022-01-02 01:20:00 215 False False True 1
2999997 109 2022-01-02 01:35:00 218 False False True 1
2999998 109 2022-01-02 01:50:00 222 False False True 1
2999999 109 2022-01-02 02:05:00 220 False False True 2

3000000 rows × 8 columns

Añadir columna: Día de la semana "Day_of_Week"¶

In [11]:
# Convertir la columna 'Measurement_date' a formato de fecha de Pandas
df['Measurement_date'] = pd.to_datetime(df['Measurement_date'])

# Extraer el día de la semana de la columna 'Measurement_date'
df['Day_of_Week'] = df['Measurement_date'].dt.day_name()

# Mostrar el resultado
df
Out[11]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
0 0 2020-06-09 19:08:00 99 True False False 19 Tuesday
1 0 2020-06-09 19:23:00 92 True False False 19 Tuesday
2 0 2020-06-09 19:38:00 86 True False False 19 Tuesday
3 0 2020-06-09 19:53:00 85 True False False 19 Tuesday
4 0 2020-06-09 20:08:00 85 True False False 20 Tuesday
... ... ... ... ... ... ... ... ... ...
2999995 109 2022-01-02 01:05:00 207 False False True 1 Sunday
2999996 109 2022-01-02 01:20:00 215 False False True 1 Sunday
2999997 109 2022-01-02 01:35:00 218 False False True 1 Sunday
2999998 109 2022-01-02 01:50:00 222 False False True 1 Sunday
2999999 109 2022-01-02 02:05:00 220 False False True 2 Sunday

3000000 rows × 9 columns

Mostrar los tipos de datos del dataframe¶

In [12]:
df.dtypes
Out[12]:
Patient_ID                   int32
Measurement_date    datetime64[ns]
Measurement_time            object
Measurement                  int64
In_Range                      bool
Hypoglycemia                  bool
Hyperglycemia                 bool
Hour_of_Day                  int64
Day_of_Week                 object
dtype: object

Convertir los datos "Measurement_time" de String (Object) a formato hora (datetime) y convertir "Day_of_Week" de String (Object) a formato entero (INT) para poder manejarlo con pandas¶

In [13]:
# Convierte la columna 'Measurement_time' en un objeto datetime de Pandas. En formato '%H:%M:%S' para la hora
df['Measurement_time'] = pd.to_datetime(df['Measurement_time'], format='%H:%M:%S')

# Extraer el día de la semana como un número entero de la columna 'Measurement_date'. 0 es lunes y 6 es domingo
df['Day_of_Week'] = df['Measurement_date'].dt.dayofweek

# Muestra los tipos de datos de cada columna en el DataFrame
df.dtypes
Out[13]:
Patient_ID                   int32
Measurement_date    datetime64[ns]
Measurement_time    datetime64[ns]
Measurement                  int64
In_Range                      bool
Hypoglycemia                  bool
Hyperglycemia                 bool
Hour_of_Day                  int64
Day_of_Week                  int64
dtype: object

Preparación de Series Temporales¶

Las series temporales son una colección de puntos de datos recogidos a intervalos regulares de tiempo. En este contexto, son esenciales para la detección de tendencias, predicciones y patrones. Estos análisis temporales son vitales en numerosos campos, incluyendo las finanzas, la economía, y en este caso específico, en el monitoreo de la glucosa, donde se utilizan para prever niveles de glucosa.

Preparación de Series Temporales:

La preparación se enfoca para la construcción de un modelo basado en series temporales el cual se va a diseñar para predecir los niveles de glucosa en un periodo de 15 minutos. Para lograr esto, el conjunto de datos en series temporales se va a preparar con intervalos de 15 minutos teniendo observaciones equidistantes en el tiempo entre las fechas de medición.

Posterior a la preparación de Series Temporales:

Posterior a la preparación se va a entrenar el modelo con una longitud de secuencia (seq_length) de 8, que se refiere a la cantidad de pesos temporales que se utilizan como entrada para predecir la salida. Para predecir los próximos 15 minutos se utiliza un bucle for. Esto permite generar múltiples predicciones en secuencia. Adicionalmente, se utilizado la función pd.date_range() de la biblioteca de Python pandas. Esta función, con un intervalo de 15 minutos y un valor de 'períodos' establecido en 3, crea un rango de fechas que se inicia desde la última fecha conocida (contada como el primer periodo) y puede generar varias fechas adicionales, cada una en un intervalo de 15 minutos. De esta manera, el total de períodos cubiertos, en este caso, abarca los próximos 15 minutos desde la última fecha registrada.

Crear un nuevo DataFrame con los datos en Series Temporal¶

Se crea un nuevo DataFrame que organiza los datos en intervalos de tiempo regulares en frecuencias de 15 minutos. Además, se han rellenado los valores faltantes mediante la interpolación lineal, lo que permite analizar los datos como una serie temporal con observaciones equidistantes en el tiempo.

In [14]:
import pandas as pd

# Ordenar el DataFrame por fecha y hora con Datetime
df['Datetime'] = pd.to_datetime(df['Measurement_date']) + pd.to_timedelta(df['Measurement_time'].dt.hour, unit='h') + pd.to_timedelta(df['Measurement_time'].dt.minute, unit='m')
df = df.sort_values(by='Datetime')

# Crear un nuevo DataFrame con los datos en serie temporal
df = df.resample('15T', on='Datetime').agg({
    'Patient_ID': 'first',
    'Measurement_date': 'first',
    'Measurement_time': 'first',
    'Measurement': 'mean',
    'In_Range': 'first',
    'Hypoglycemia': 'first',
    'Hyperglycemia': 'first',
    'Hour_of_Day': 'first',
    'Day_of_Week': 'first'
})

# Rellenar los valores faltantes
df['Measurement'].interpolate(method='linear', inplace=True)

# Redondear los valores en la columna 'Measurement'
df['Measurement'] = df['Measurement'].round()

# Convertir el tipo de datos de cada columna al tipo de datos original
df = df.astype({
    'Patient_ID': 'Int32',
    'Measurement': 'Int64',
    'In_Range': 'bool',
    'Hypoglycemia': 'bool',
    'Hyperglycemia': 'bool',
    'Hour_of_Day': 'Int64',
    'Day_of_Week': 'Int64'
})

Comprobar si hay valores nulos y faltantes¶

In [15]:
# Verificar si hay valores nulos
if df.isnull().any().any():
    print("Hay valores nulos en el dataframe")
else:
    print("No hay valores nulos en el dataframe")

# Verificar si hay valores faltantes
if df.isna().any().any():
    print("Hay valores faltantes en el dataframe")
else:
    print("No hay valores faltantes en el dataframe")
Hay valores nulos en el dataframe
Hay valores faltantes en el dataframe

Muestra los nombres numéricos de los pacientes, son númericos del 0 al 109 siendo un total de 110 pacientes¶

In [16]:
# Obtener una lista de pacientes únicos
pacientes = df["Patient_ID"].unique()
print(pacientes)

print("\n")
# Crear un DataFrame transpuesto con la lista de pacientes
df_pacientes = pd.DataFrame(pacientes).T

# Mostrar el DataFrame transpuesto
print(df_pacientes.to_string(index=False, header=False))

# Encontrar los ID de pacientes faltantes
missing_patients = [patient for patient in range(0, 110) if patient not in pacientes]
print(f"Paciente faltante: {missing_patients}")
<IntegerArray>
[  83, <NA>,   48,   92,   11,   24,    5,   91,   22,    2,
 ...
   10,   44,   13,  103,   64,   57,   38,   33,   65,    7]
Length: 110, dtype: Int32


83 <NA> 48 92 11 24 5 91 22 2 6 40 47 12 43 69 55 60 71 100 75 67 93 59 25 17 74 87 54 21 4 1 0 29 96 84 14 104 86 94 88 3 78 62 99 53 34 68 80 82 50 16 30 39 46 41 77 19 42 28 58 102 95 61 18 81 35 97 98 107 106 26 70 9 49 45 23 73 32 63 27 76 79 15 8 36 56 52 72 31 85 101 108 89 109 90 105 51 20 37 10 44 13 103 64 57 38 33 65 7
Paciente faltante: [66]

Cantidad de todas las filas con valores faltantes¶

In [17]:
# Verificar valores faltantes o nulos en todo el DataFrame
print(df.isnull().sum())

# Verificar valores faltantes o nulos en una columna específica
print(df['Patient_ID'].isnull().sum())
Patient_ID          6609
Measurement_date    6609
Measurement_time    6609
Measurement            0
In_Range               0
Hypoglycemia           0
Hyperglycemia          0
Hour_of_Day         6609
Day_of_Week         6609
dtype: int64
6609

Todos los pacientes menos el paciente 66 (Paciente faltante)¶

In [18]:
# Crear un nuevo DataFrame que excluye filas donde la columna Patient_ID tiene un valor de 66
df_clean = df[df['Patient_ID'] != 66]

# Verificar valores faltantes o nulos en todo el DataFrame
print(df_clean.isnull().sum())

# Verificar valores faltantes o nulos en una columna específica
print(df_clean['Patient_ID'].isnull().sum())
Patient_ID          0
Measurement_date    0
Measurement_time    0
Measurement         0
In_Range            0
Hypoglycemia        0
Hyperglycemia       0
Hour_of_Day         0
Day_of_Week         0
dtype: int64
0

Eliminar valores faltantes, eliminando el paciente "66"¶

In [19]:
# Crear un nuevo DataFrame que excluye filas donde la columna Patient_ID tiene un valor de 66
df = df[df['Patient_ID'] != 66]

Cantidad de todas las filas con valores faltantes¶

In [20]:
# Verificar valores faltantes o nulos en todo el DataFrame
print(df.isnull().sum())

# Verificar valores faltantes o nulos en una columna específica
print(df['Patient_ID'].isnull().sum())
Patient_ID          0
Measurement_date    0
Measurement_time    0
Measurement         0
In_Range            0
Hypoglycemia        0
Hyperglycemia       0
Hour_of_Day         0
Day_of_Week         0
dtype: int64
0

Comprobar si hay valores nulos y faltantes¶

In [21]:
# Verificar si hay valores nulos
if df.isnull().any().any():
    print("Hay valores nulos en el dataframe")
else:
    print("No hay valores nulos en el dataframe")

# Verificar si hay valores faltantes
if df.isna().any().any():
    print("Hay valores faltantes en el dataframe")
else:
    print("No hay valores faltantes en el dataframe")
No hay valores nulos en el dataframe
No hay valores faltantes en el dataframe

3. Minería de datos¶

En esta sección se va a llevar a cabo toda la identificación de patrones y relaciones, análisis exploratorio y estadístico y el uso de algoritmos de aprendizaje automático profundo RNN y CRNN.

Muestra el dataframe preparado¶

Ahora los pacientes son del 0 hasta el 109 y las columnas Measurement_date y Measurement_time son datatime permitiendo manejar sus fechas con pandas

In [22]:
df
Out[22]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-02-21 19:45:00 83 2018-02-21 1900-01-01 19:46:00 121 True False False 19 2
2018-02-21 20:00:00 83 2018-02-21 1900-01-01 20:01:00 124 True False False 20 2
2018-02-21 20:15:00 83 2018-02-21 1900-01-01 20:17:00 131 True False False 20 2
2018-02-21 20:30:00 83 2018-02-21 1900-01-01 20:32:00 128 True False False 20 2
2018-02-21 20:45:00 83 2018-02-21 1900-01-01 20:47:00 129 True False False 20 2
... ... ... ... ... ... ... ... ... ...
2022-03-21 19:45:00 27 2022-03-21 1900-01-01 19:48:00 264 False False True 19 0
2022-03-21 20:00:00 27 2022-03-21 1900-01-01 20:03:00 269 False False True 20 0
2022-03-21 20:15:00 27 2022-03-21 1900-01-01 20:18:00 283 False False True 20 0
2022-03-21 20:30:00 27 2022-03-21 1900-01-01 20:33:00 315 False False True 20 0
2022-03-21 20:45:00 27 2022-03-21 1900-01-01 20:48:00 327 False False True 20 0

136340 rows × 9 columns

In [23]:
df.dtypes
Out[23]:
Patient_ID                   Int32
Measurement_date    datetime64[ns]
Measurement_time    datetime64[ns]
Measurement                  Int64
In_Range                      bool
Hypoglycemia                  bool
Hyperglycemia                 bool
Hour_of_Day                  Int64
Day_of_Week                  Int64
dtype: object

Muestra los nombres numéricos de los pacientes, son númericos del 0 al 109 siendo un total de 110 pacientes¶

In [24]:
pacientes = df["Patient_ID"].unique()
print(pacientes)

print("\n")
df_pacientes = pd.DataFrame(pacientes).T

# Display the transposed DataFrame
print(df_pacientes.to_string(index=False, header=False))

# Find missing patient IDs
missing_patients = [patient for patient in range(0, 110) if patient not in pacientes]
print(f"Paciente faltante: {missing_patients}")
<IntegerArray>
[ 83,  48,  92,  11,  24,   5,  91,  22,   2,   6,
 ...
  10,  44,  13, 103,  64,  57,  38,  33,  65,   7]
Length: 109, dtype: Int32


83 48 92 11 24 5 91 22 2 6 40 47 12 43 69 55 60 71 100 75 67 93 59 25 17 74 87 54 21 4 1 0 29 96 84 14 104 86 94 88 3 78 62 99 53 34 68 80 82 50 16 30 39 46 41 77 19 42 28 58 102 95 61 18 81 35 97 98 107 106 26 70 9 49 45 23 73 32 63 27 76 79 15 8 36 56 52 72 31 85 101 108 89 109 90 105 51 20 37 10 44 13 103 64 57 38 33 65 7
Paciente faltante: [66]

El número de veces que aparece cada uno de los pacientes. De más a menos veces que se han escaneado¶

In [25]:
# Obtener el conteo de valores para la columna 'Patient_ID'
df_vc = df['Patient_ID'].value_counts().reset_index()

# Renombrar las columnas para que sean más descriptivas
df_vc.columns = ['Patient_ID', 'Count']

# Mostrar el resultado
df_vc
Out[25]:
Patient_ID Count
0 24 11987
1 11 11332
2 22 10409
3 83 9198
4 92 8509
... ... ...
104 33 38
105 79 27
106 57 26
107 65 20
108 103 11

109 rows × 2 columns

Mostrar las fechas de inicio y final del estudio de los pacientes¶

In [26]:
df.groupby('Patient_ID')['Measurement_date'].agg(['min', 'max'])
Out[26]:
min max
Patient_ID
0 2020-06-12 2022-03-18
1 2020-06-10 2022-03-06
2 2019-01-27 2022-03-19
3 2020-10-10 2022-03-11
4 2020-06-10 2022-03-17
... ... ...
105 2021-10-29 2022-03-15
106 2021-06-18 2022-03-14
107 2021-06-17 2022-03-11
108 2021-10-25 2022-03-18
109 2021-10-28 2022-01-01

109 rows × 2 columns

Muestra el paciente 1 que es el número 0 (podemos ver que contiene todas sus columnas y filas)¶

In [27]:
df.loc[df["Patient_ID"] == 0]
Out[27]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2020-06-12 00:15:00 0 2020-06-12 1900-01-01 00:16:00 121 True False False 0 4
2020-06-12 00:30:00 0 2020-06-12 1900-01-01 00:31:00 123 True False False 0 4
2020-06-12 00:45:00 0 2020-06-12 1900-01-01 00:46:00 132 True False False 0 4
2020-06-12 10:00:00 0 2020-06-12 1900-01-01 10:01:00 155 True False False 10 4
2020-06-12 10:15:00 0 2020-06-12 1900-01-01 10:16:00 158 True False False 10 4
... ... ... ... ... ... ... ... ... ...
2022-03-18 02:00:00 0 2022-03-18 1900-01-01 02:00:00 170 False False True 2 4
2022-03-18 02:15:00 0 2022-03-18 1900-01-01 02:15:00 170 False False True 2 4
2022-03-18 06:15:00 0 2022-03-18 1900-01-01 06:15:00 154 False False True 6 4
2022-03-18 07:45:00 0 2022-03-18 1900-01-01 07:45:00 153 False False True 7 4
2022-03-18 11:15:00 0 2022-03-18 1900-01-01 11:15:00 173 False False True 11 4

1675 rows × 9 columns

**Dataframe más pequeño para poder manejarlo (5 pacientes)**

Se ha realizado una reducción de datos del DataFrame debido a que la cantidad de información era demasiado grande para ser procesada por la capacidad de cómputo disponible en la computadora utilizada.

Obtener los 5 pacientes con más datos¶

Podemos ver que hay 51435 filas.

In [28]:
# Crear un nuevo DataFrame con los 5 pacientes con más datos
top_5_pacientes = df.groupby('Patient_ID')['Measurement_time'].count().sort_values(ascending=False).head(5).index
df_top_5_pacientes = df[df['Patient_ID'].isin(top_5_pacientes)]
df_top_5_pacientes
Out[28]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-02-21 19:45:00 83 2018-02-21 1900-01-01 19:46:00 121 True False False 19 2
2018-02-21 20:00:00 83 2018-02-21 1900-01-01 20:01:00 124 True False False 20 2
2018-02-21 20:15:00 83 2018-02-21 1900-01-01 20:17:00 131 True False False 20 2
2018-02-21 20:30:00 83 2018-02-21 1900-01-01 20:32:00 128 True False False 20 2
2018-02-21 20:45:00 83 2018-02-21 1900-01-01 20:47:00 129 True False False 20 2
... ... ... ... ... ... ... ... ... ...
2022-03-14 07:00:00 83 2022-03-14 1900-01-01 07:00:00 160 True False False 7 0
2022-03-14 07:15:00 83 2022-03-14 1900-01-01 07:15:00 164 True False False 7 0
2022-03-14 10:00:00 83 2022-03-14 1900-01-01 10:00:00 182 True False False 10 0
2022-03-14 10:45:00 83 2022-03-14 1900-01-01 10:45:00 168 True False False 10 0
2022-03-15 23:45:00 22 2022-03-15 1900-01-01 23:46:00 155 True False False 23 1

51435 rows × 9 columns

Mostrar los 5 pacientes con mayor número de mediciones desde el inicio hasta el final del estudio y su cantidad de mediciones totales junto su media total¶

In [29]:
# Agrupar el DataFrame 'df' por el ID de paciente ('Patient_ID') y contar el número de mediciones ('Measurement_time') que cada paciente ha realizado.
# Luego ordenar los resultados de manera descendente ('sort_values(ascending=False)') y seleccionar los primeros 5 pacientes con más mediciones ('head(5)').
top_5_pacientes = df.groupby('Patient_ID')['Measurement_time'].count().sort_values(ascending=False).head(5)

# Crear un nuevo DataFrame 'df_top_5_pacientes_modificado' que contiene los mismos datos que 'top_5_pacientes',
# pero reseteamos el índice para que el ID de paciente y el número de mediciones estén en columnas separadas.
df_top_5_pacientes_modificado = top_5_pacientes.reset_index()

# Renombrar las columnas del DataFrame 'df_top_5_pacientes_modificado'.
df_top_5_pacientes_modificado.columns = ['Patient_ID', 'Num_Mediciones']

# Agrupar el DataFrame 'df' por el ID de paciente ('Patient_ID') y obtener la fecha mínima ('min') y máxima ('max') de las mediciones.
# Luego resetear el índice y se guarda en el DataFrame 'fechas_min_max'.
fechas_min_max = df.groupby('Patient_ID')['Measurement_date'].agg(['min', 'max']).reset_index()

# Unir el DataFrame 'df_top_5_pacientes_modificado' y el DataFrame 'fechas_min_max' en una nueva DataFrame 'df_top_5_pacientes_modificado'
# Utilizar el ID de paciente ('Patient_ID') como clave de unión.
df_top_5_pacientes_modificado = df_top_5_pacientes_modificado.merge(fechas_min_max, on='Patient_ID')

# Agrupar el DataFrame 'df' por el ID de paciente ('Patient_ID') y calcular la media de la columna 'Measurement' para cada paciente
media_glucosa = df.groupby('Patient_ID')['Measurement'].mean()

# Crear un nuevo DataFrame 'df_media_glucosa' que contiene los mismos datos que 'media_glucosa',
# pero reseteamos el índice para que el ID de paciente y la media de glucosa estén en columnas separadas.
df_media_glucosa = media_glucosa.reset_index()

# Renombrar las columnas del DataFrame 'df_media_glucosa'.
df_media_glucosa.columns = ['Patient_ID', 'Media_Glucosa']

# Unir el DataFrame 'df_top_5_pacientes_modificado' y el DataFrame 'df_media_glucosa' en una nueva DataFrame 'df_top_5_pacientes_modificado'
# Utilizar el ID de paciente ('Patient_ID') como clave de unión.
df_top_5_pacientes_modificado = df_top_5_pacientes_modificado.merge(df_media_glucosa, on='Patient_ID')
df_top_5_pacientes_modificado
Out[29]:
Patient_ID Num_Mediciones min max Media_Glucosa
0 24 11987 2018-07-13 2022-03-12 151.588888
1 11 11332 2018-06-12 2022-02-25 150.851747
2 22 10409 2018-11-06 2022-03-15 157.267653
3 83 9198 2018-02-21 2022-03-14 144.642314
4 92 8509 2018-06-06 2022-01-10 152.568692

Se va a manejar las fechas de verano (junio, julio, agosto) y de invierno (diciembre, enero, febrero y marzo) ya que el calor influye en glucosa del paciente¶

Verano¶

In [30]:
df_verano = df_top_5_pacientes[df_top_5_pacientes['Measurement_date'].dt.month.isin([6,7,8])]
df_verano
Out[30]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-06-06 11:30:00 92 2018-06-06 1900-01-01 11:38:00 372 False False True 11 2
2018-06-06 11:45:00 92 2018-06-06 1900-01-01 11:53:00 365 False False True 11 2
2018-06-06 12:00:00 92 2018-06-06 1900-01-01 12:08:00 345 False False True 12 2
2018-06-06 12:15:00 92 2018-06-06 1900-01-01 12:23:00 327 False False True 12 2
2018-06-06 12:30:00 92 2018-06-06 1900-01-01 12:38:00 305 False False True 12 2
... ... ... ... ... ... ... ... ... ...
2021-08-30 23:30:00 83 2021-08-30 1900-01-01 23:30:00 150 True False False 23 0
2021-08-31 16:45:00 83 2021-08-31 1900-01-01 16:45:00 155 False False True 16 1
2021-08-31 17:45:00 83 2021-08-31 1900-01-01 17:45:00 157 True False False 17 1
2021-08-31 18:00:00 83 2021-08-31 1900-01-01 18:00:00 158 True False False 18 1
2021-08-31 18:45:00 83 2021-08-31 1900-01-01 18:45:00 161 True False False 18 1

13634 rows × 9 columns

Invierno¶

In [31]:
df_inverno = df_top_5_pacientes[df_top_5_pacientes['Measurement_date'].dt.month.isin([12,1,2,3])]
df_inverno
Out[31]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-02-21 19:45:00 83 2018-02-21 1900-01-01 19:46:00 121 True False False 19 2
2018-02-21 20:00:00 83 2018-02-21 1900-01-01 20:01:00 124 True False False 20 2
2018-02-21 20:15:00 83 2018-02-21 1900-01-01 20:17:00 131 True False False 20 2
2018-02-21 20:30:00 83 2018-02-21 1900-01-01 20:32:00 128 True False False 20 2
2018-02-21 20:45:00 83 2018-02-21 1900-01-01 20:47:00 129 True False False 20 2
... ... ... ... ... ... ... ... ... ...
2022-03-14 07:00:00 83 2022-03-14 1900-01-01 07:00:00 160 True False False 7 0
2022-03-14 07:15:00 83 2022-03-14 1900-01-01 07:15:00 164 True False False 7 0
2022-03-14 10:00:00 83 2022-03-14 1900-01-01 10:00:00 182 True False False 10 0
2022-03-14 10:45:00 83 2022-03-14 1900-01-01 10:45:00 168 True False False 10 0
2022-03-15 23:45:00 22 2022-03-15 1900-01-01 23:46:00 155 True False False 23 1

17535 rows × 9 columns

La media de la glucosa según los distintos dataframes¶

In [32]:
# Calcular las medias para cada DataFrame
media_df = df['Measurement'].mean()
media_df_top_5_pacientes = df_top_5_pacientes['Measurement'].mean()
media_df_verano = df_verano['Measurement'].mean()
media_df_inverno = df_inverno['Measurement'].mean()

# Crear un nuevo DataFrame con los valores de las medias
df_medias = pd.DataFrame({'DataFrame': ['df (completo)', 'df_top_5_pacientes', 'df_verano (top_5_pacientes)', 'df_inverno (top_5_pacientes)'],
                          'Media': [media_df, media_df_top_5_pacientes, media_df_verano, media_df_inverno]})
# Mostrar el resultado
df_medias
Out[32]:
DataFrame Media
0 df (completo) 154.892599
1 df_top_5_pacientes 151.495557
2 df_verano (top_5_pacientes) 144.856829
3 df_inverno (top_5_pacientes) 154.311263

Valores que están en rango¶

In [33]:
# Seleccionar las filas con Measurement (medición de glucosa) entre 70 mg/dl y 180 mg/dl (Tiempo en rango ideal según TIR)
df_top_5_pacientes[(df_top_5_pacientes['Measurement'] >= 70) & (df_top_5_pacientes['Measurement'] <= 180)]
df_top_5_pacientes
Out[33]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-02-21 19:45:00 83 2018-02-21 1900-01-01 19:46:00 121 True False False 19 2
2018-02-21 20:00:00 83 2018-02-21 1900-01-01 20:01:00 124 True False False 20 2
2018-02-21 20:15:00 83 2018-02-21 1900-01-01 20:17:00 131 True False False 20 2
2018-02-21 20:30:00 83 2018-02-21 1900-01-01 20:32:00 128 True False False 20 2
2018-02-21 20:45:00 83 2018-02-21 1900-01-01 20:47:00 129 True False False 20 2
... ... ... ... ... ... ... ... ... ...
2022-03-14 07:00:00 83 2022-03-14 1900-01-01 07:00:00 160 True False False 7 0
2022-03-14 07:15:00 83 2022-03-14 1900-01-01 07:15:00 164 True False False 7 0
2022-03-14 10:00:00 83 2022-03-14 1900-01-01 10:00:00 182 True False False 10 0
2022-03-14 10:45:00 83 2022-03-14 1900-01-01 10:45:00 168 True False False 10 0
2022-03-15 23:45:00 22 2022-03-15 1900-01-01 23:46:00 155 True False False 23 1

51435 rows × 9 columns

In [34]:
# Filtrar solo las mediciones del día 9 de junio de 2020
df_day = df_top_5_pacientes[(df_top_5_pacientes['Measurement_date'] == '2020-06-09')]

# Contar cuántas mediciones están dentro del rango y calcular el porcentaje
num_in_range = len(df_day[(df_day['Measurement'] >= 70) & (df_day['Measurement'] <= 180)])
percent_in_range = num_in_range / len(df_day) * 100

print('Porcentaje de mediciones dentro del rango: {:.2f}%'.format(percent_in_range))
Porcentaje de mediciones dentro del rango: 100.00%

**Análisis del Paciente 22**

  • Measurement_date: Extraer el día de la semana (ya que no es lo mismo de lunes a viernes que de sabado a domingo o principios de mes que a finales de mes)
  • Measuremenet_time: Muestra el momento del día ya que no es lo mismo que sea por la noche a que sea por el día, tampoco es lo mismo que sea por la mañana a que sea por la tarde, o antes o después del desayuno, comida o cena. “factor del alba, depende de la hora del dia la sensibilidad a la insulina es distinta".
  • Measurement: Muestra los niveles de glucosa en cada medición. Se utiliza para calcular métricas como el tiempo en rango, la hipoglucemia y la hiperglucemia, que brindan información sobre el control glucémico.
  • Hypoglycemia (Hipoglucemia): Es de tipo booleano y se extrae comparando cada medición de glucosa con un umbral predefinido, por ejemplo, 70 mg/dL. Si el valor de la medición es menor que este umbral, se establece la columna "Hypoglycemia" como Verdadero (True); de lo contrario, se establece como Falso (False). Esto indica si la medición muestra niveles de glucosa por debajo del umbral de hipoglucemia.
  • Hyperglycemia (Hiperglucemia): Es de tipo booleano y se extrae comparando cada medición de glucosa con otro umbral predefinido, por ejemplo, 180 mg/dL. Si el valor de la medición es mayor que este umbral, se establece la columna "Hyperglycemia" como Verdadero (True); de lo contrario, se establece como Falso (False). Esto indica si la medición muestra niveles de glucosa por encima del umbral de hiperglucemia.
  • Hour_of_Day: Se deriva del campo "Measurement_time" y representa la hora en que se tomó cada medición. Ayuda a identificar patrones diarios en los niveles de glucosa.
  • Day_of_Week: Se deriva de la columna "Measurement_date" y muestra el día de la semana en que se realizó cada medición. Permite capturar patrones semanales en los niveles de glucosa.

Todos los datos del paciente 22¶

In [35]:
# Filtrar los datos para el paciente 22
df_paciente_22 = df_top_5_pacientes[df_top_5_pacientes['Patient_ID'] == 22]

# Imprimir los resultados
df_paciente_22
Out[35]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-11-06 06:45:00 22 2018-11-06 1900-01-01 06:45:00 134 True False False 6 1
2018-11-06 07:00:00 22 2018-11-06 1900-01-01 07:00:00 134 True False False 7 1
2018-11-06 07:15:00 22 2018-11-06 1900-01-01 07:15:00 139 True False False 7 1
2018-11-06 07:30:00 22 2018-11-06 1900-01-01 07:30:00 145 True False False 7 1
2018-11-06 07:45:00 22 2018-11-06 1900-01-01 07:45:00 147 True False False 7 1
... ... ... ... ... ... ... ... ... ...
2022-03-08 11:30:00 22 2022-03-08 1900-01-01 11:30:00 179 True False False 11 1
2022-03-08 12:30:00 22 2022-03-08 1900-01-01 12:30:00 173 True False False 12 1
2022-03-08 12:45:00 22 2022-03-08 1900-01-01 12:45:00 171 True False False 12 1
2022-03-08 13:15:00 22 2022-03-08 1900-01-01 13:15:00 165 True False False 13 1
2022-03-15 23:45:00 22 2022-03-15 1900-01-01 23:46:00 155 True False False 23 1

10409 rows × 9 columns

Extraer la media de la glucosa entre semana a diferencia con el fin de semana¶

Se va a calcular del mismo año y mes la glucosa media según si es entre semana o fin de semana.

El paciente con mayor cantidad de mediciones en un mes completo¶

Muestra el paciente con la mayor cantidad de mediciones y con su total de mediciones, además de la fecha correspondiente.

El paciente ID 22 en un mes tiene 1565 mediciones 1/2019.

In [36]:
# Agrupar los datos por Patient_ID y mes de Measurement_date
grouped_df = df_top_5_pacientes.groupby(['Patient_ID', df_top_5_pacientes['Measurement_date'].dt.to_period('M')])

# Encontrar el Patient_ID y la fecha (mes y año) para el paciente con la mayor cantidad de mediciones
result = grouped_df.size().idxmax()

# Calcular el total de mediciones para el paciente con la mayor cantidad de mediciones
total_measurements = grouped_df.size().max()

# Extraer el Patient_ID, año y mes del resultado
patient_id, date = result
year, month = date.year, date.month

# Imprimir los resultados
print(f'El paciente con ID {patient_id} tiene el mayor número de mediciones en un mes con un total de {total_measurements} mediciones en {month}/{year}.')
El paciente con ID 22 tiene el mayor número de mediciones en un mes con un total de 1565 mediciones en 1/2019.

Mostrar el paciente 22 (2019)¶

In [37]:
# Filtrar los datos para el paciente 83 del año 2021
df_paciente_22_2019 = df_top_5_pacientes[(df_top_5_pacientes['Patient_ID'] == 22) & (df_top_5_pacientes['Measurement_date'].dt.year == 2019)]

# Imprimir los resultados
df_paciente_22_2019
Out[37]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2019-01-01 00:00:00 22 2019-01-01 1900-01-01 00:00:00 194 True False False 0 1
2019-01-01 00:15:00 22 2019-01-01 1900-01-01 00:15:00 205 True False False 0 1
2019-01-01 00:30:00 22 2019-01-01 1900-01-01 00:30:00 216 True False False 0 1
2019-01-01 00:45:00 22 2019-01-01 1900-01-01 00:45:00 217 True False False 0 1
2019-01-01 01:00:00 22 2019-01-01 1900-01-01 01:00:00 225 True False False 1 1
... ... ... ... ... ... ... ... ... ...
2019-12-31 15:30:00 22 2019-12-31 1900-01-01 15:36:00 145 True False False 15 1
2019-12-31 15:45:00 22 2019-12-31 1900-01-01 15:51:00 140 True False False 15 1
2019-12-31 16:00:00 22 2019-12-31 1900-01-01 16:06:00 133 True False False 16 1
2019-12-31 16:15:00 22 2019-12-31 1900-01-01 16:21:00 134 True False False 16 1
2019-12-31 16:30:00 22 2019-12-31 1900-01-01 16:36:00 133 True False False 16 1

6099 rows × 9 columns

Mostrar el mes 1 completo del paciente 22 (1/2019)¶

In [38]:
# pd.set_option('display.max_rows', None)
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_top_5_pacientes[(df_top_5_pacientes['Patient_ID'] == 22) & (df_top_5_pacientes['Measurement_date'].dt.year == 2019) & (df_top_5_pacientes['Measurement_date'].dt.month == 1)]

# Imprimir los resultados
df_paciente_22_1_2019
Out[38]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2019-01-01 00:00:00 22 2019-01-01 1900-01-01 00:00:00 194 True False False 0 1
2019-01-01 00:15:00 22 2019-01-01 1900-01-01 00:15:00 205 True False False 0 1
2019-01-01 00:30:00 22 2019-01-01 1900-01-01 00:30:00 216 True False False 0 1
2019-01-01 00:45:00 22 2019-01-01 1900-01-01 00:45:00 217 True False False 0 1
2019-01-01 01:00:00 22 2019-01-01 1900-01-01 01:00:00 225 True False False 1 1
... ... ... ... ... ... ... ... ... ...
2019-01-31 22:45:00 22 2019-01-31 1900-01-01 22:52:00 146 True False False 22 3
2019-01-31 23:00:00 22 2019-01-31 1900-01-01 23:07:00 145 True False False 23 3
2019-01-31 23:15:00 22 2019-01-31 1900-01-01 23:22:00 144 True False False 23 3
2019-01-31 23:30:00 22 2019-01-31 1900-01-01 23:37:00 141 True False False 23 3
2019-01-31 23:45:00 22 2019-01-31 1900-01-01 23:52:00 145 True False False 23 3

1565 rows × 9 columns

Verificar que todos los días el paciente 22 tiene mediciones y no tiene valores nulos (1/2019)¶

In [39]:
# Crear una copia del dataframe df_top_5_pacientes
df_paciente_22_1_2019_copia = df_paciente_22_1_2019.copy()

# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Verificar que todos los días del mes tienen mediciones
all_days_have_measurements = df_paciente_22_1_2019['Measurement_date'].nunique() == df_paciente_22_1_2019['Measurement_date'].iloc[0].days_in_month

# Verificar que no hay valores nulos en la columna Measurement
no_null_values = df_paciente_22_1_2019_copia['Measurement'].notnull().all()

# Imprimir los resultados
if all_days_have_measurements:
    print('Todos los días del mes tienen mediciones.')
else:
    print('No todos los días del mes tienen mediciones.')

if no_null_values:
    print('No hay valores nulos en la columna Measurement.')
else:
    print('Hay valores nulos en la columna Measurement.')
No todos los días del mes tienen mediciones.
No hay valores nulos en la columna Measurement.

Gráfico de barras y de líneas que muestra el número de mediciones para cada día del mes del paciente 22 (1/2019)¶

In [40]:
# Gráfico de barras
# Agrupar por día y contar el número de mediciones
conteo_mediciones = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.day)['Measurement'].size().reset_index()
conteo_mediciones.columns = ['Día', 'Num_Mediciones']

# Crear una figura y un eje
fig, ax = plt.subplots()

# Crear el gráfico de barras
ax.bar(conteo_mediciones['Día'], conteo_mediciones['Num_Mediciones'])

# Establecer las etiquetas de los ejes
ax.set_xlabel('Día')
ax.set_ylabel('Número de mediciones')

# Mostrar el gráfico de barras
plt.show()

# Gráfico de líneas
# Agrupar por día y contar el número de mediciones
conteo_mediciones = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.day)['Measurement'].size().reset_index()
conteo_mediciones.columns = ['Día', 'Num_Mediciones']

# Crear una figura y un eje
fig, ax = plt.subplots()

# Crear el gráfico de líneas
ax.plot(conteo_mediciones['Día'], conteo_mediciones['Num_Mediciones'])

# Establecer las etiquetas de los ejes
ax.set_xlabel('Día')
ax.set_ylabel('Número de mediciones')

# Mostrar el gráfico de líneas
plt.show()

Verificar que no hay valores anómalos en la glucosa (Measurement) del paciente 22 (2019)¶

El freestlye libre 2 no puede capturar valores de glucosa inferiores a 40 o superiores a 500

In [41]:
# Verificar si hay valores anómalos en la columna Measurement
anomalous_values = df_paciente_22_2019[(df_paciente_22_2019['Measurement'] < 40) | (df_paciente_22_2019['Measurement'] > 500)]

# Imprimir los resultados
if anomalous_values.empty:
    print('No hay valores anómalos en la columna Measurement.')
else:
    print('Hay valores anómalos en la columna Measurement:')
    print(anomalous_values)
No hay valores anómalos en la columna Measurement.

Extrae los días y la media de la glucosa entre semana (lunes a viernes) del paciente 22 (1/2019)¶

In [42]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Filtrar solo los días de entre semana (lunes a viernes)
df_top_5_pacientes_dias = df_paciente_22_1_2019[df_paciente_22_1_2019['Measurement_date'].dt.dayofweek < 5]

# Calcular la media de la columna Measurement para los días de entre semana
mean_measurement = df_top_5_pacientes_dias['Measurement'].mean()

# Imprimir el resultado
print(f'La media de la glucosa para los días de entre semana es: {mean_measurement:.2f}')
df_top_5_pacientes_dias
La media de la glucosa para los días de entre semana es: 155.27
Out[42]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2019-01-01 00:00:00 22 2019-01-01 1900-01-01 00:00:00 194 True False False 0 1
2019-01-01 00:15:00 22 2019-01-01 1900-01-01 00:15:00 205 True False False 0 1
2019-01-01 00:30:00 22 2019-01-01 1900-01-01 00:30:00 216 True False False 0 1
2019-01-01 00:45:00 22 2019-01-01 1900-01-01 00:45:00 217 True False False 0 1
2019-01-01 01:00:00 22 2019-01-01 1900-01-01 01:00:00 225 True False False 1 1
... ... ... ... ... ... ... ... ... ...
2019-01-31 22:45:00 22 2019-01-31 1900-01-01 22:52:00 146 True False False 22 3
2019-01-31 23:00:00 22 2019-01-31 1900-01-01 23:07:00 145 True False False 23 3
2019-01-31 23:15:00 22 2019-01-31 1900-01-01 23:22:00 144 True False False 23 3
2019-01-31 23:30:00 22 2019-01-31 1900-01-01 23:37:00 141 True False False 23 3
2019-01-31 23:45:00 22 2019-01-31 1900-01-01 23:52:00 145 True False False 23 3

1099 rows × 9 columns

Gráfico de barras y de líneas con los días y la media de la glucosa entre semana (lunes a viernes) del paciente 22 (1/2019)¶

In [43]:
import matplotlib.pyplot as plt
# Gráfico de barras
# Agrupar por día y calcular la media de las mediciones
mediciones_dias = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.day)['Measurement'].mean().reset_index()
mediciones_dias.columns = ['Día', 'Media_Mediciones']

# Crear una figura y un eje
fig, ax = plt.subplots()

# Crear el gráfico de barras
ax.bar(mediciones_dias['Día'], mediciones_dias['Media_Mediciones'])

# Establecer las etiquetas de los ejes
ax.set_xlabel('Día')
ax.set_ylabel('Media de mediciones')

# Mostrar el gráfico de barras
plt.show()

# Gráfico de líneas
# Crear una figura y un eje
fig, ax = plt.subplots()

# Crear el gráfico de líneas
ax.plot(mediciones_dias['Día'], mediciones_dias['Media_Mediciones'])

# Establecer las etiquetas de los ejes
ax.set_xlabel('Día')
ax.set_ylabel('Media de mediciones')

# Mostrar el gráfico de líneas
plt.show()

Extrae los días y la media de la glucosa del fin de semana (sabado y domingo) del paciente 22 (1/2019)¶

In [44]:
# Filtrar los datos para el paciente 83 en el mes 10 del año 2021
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Filtrar solo los fines de semana (sábado y domingo)
df_top_5_pacientes_dias = df_paciente_22_1_2019[df_paciente_22_1_2019['Measurement_date'].dt.dayofweek >= 5]

# Calcular la media de la columna Measurement para los fines de semana
mean_measurement = df_top_5_pacientes_dias['Measurement'].mean()

# Imprimir el resultado
print(f'La media de la glucosa para los fin de semana es: {mean_measurement:.2f}')
df_top_5_pacientes_dias
La media de la glucosa para los fin de semana es: 165.48
Out[44]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2019-01-05 00:00:00 22 2019-01-05 1900-01-01 00:00:00 216 False False True 0 5
2019-01-05 00:15:00 22 2019-01-05 1900-01-01 00:15:00 219 False False True 0 5
2019-01-05 00:30:00 22 2019-01-05 1900-01-01 00:30:00 223 False False True 0 5
2019-01-05 00:45:00 22 2019-01-05 1900-01-01 00:45:00 225 False False True 0 5
2019-01-05 01:00:00 22 2019-01-05 1900-01-01 01:00:00 190 False False True 1 5
... ... ... ... ... ... ... ... ... ...
2019-01-27 22:45:00 22 2019-01-27 1900-01-01 22:56:00 251 False False True 22 6
2019-01-27 23:00:00 22 2019-01-27 1900-01-01 23:11:00 219 False False True 23 6
2019-01-27 23:15:00 22 2019-01-27 1900-01-01 23:26:00 207 False False True 23 6
2019-01-27 23:30:00 22 2019-01-27 1900-01-01 23:41:00 194 False False True 23 6
2019-01-27 23:45:00 22 2019-01-27 1900-01-01 23:56:00 176 False False True 23 6

466 rows × 9 columns

Gráfico de barras y de líneas con los días y la media de la glucosa del fin de semana (sabado y domingo) del paciente 22 (1/2019)¶

In [45]:
import matplotlib.pyplot as plt

# Crear un nuevo DataFrame con los valores medios de Measurement para cada día del fin de semana
mediciones_dias = df_paciente_22_1_2019[df_paciente_22_1_2019['Measurement_date'].dt.dayofweek >= 5]
mediciones_dias = mediciones_dias.groupby(mediciones_dias['Measurement_date'].dt.date)['Measurement'].mean().reset_index()

# Crear un gráfico de barras con rotación x-axis labels
plt.bar(mediciones_dias['Measurement_date'], mediciones_dias['Measurement'])
plt.title('Average Glucose Measurements on Weekends in October 2021')
plt.xlabel('Date')
plt.ylabel('Average Glucose Measurement')
plt.xticks(rotation=45) # Rotar las etiquetas del eje x en 45 grados
plt.show()

# Crear un gráfico de líneas
plt.plot(mediciones_dias['Measurement_date'], mediciones_dias['Measurement'])
plt.title('Average Glucose Measurements on Weekends in October 2021')
plt.xlabel('Date')
plt.ylabel('Average Glucose Measurement')
plt.xticks(rotation=45) # Rotar las etiquetas del eje x en 45 grados
plt.show()

Extraer el día con mayor cantidad de mediciones del paciente 22 (1/2019)¶

In [46]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Agrupar los datos por día y contar el número de mediciones por día
daily_counts = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.date)['Measurement_time'].count()

# Encontrar el día con la mayor cantidad de mediciones
max_day = daily_counts.idxmax()

# Obtener la cantidad de mediciones para ese día
max_count = daily_counts.loc[max_day]

# Imprimir el resultado
print(f'El día con la mayor cantidad de mediciones es: {max_day} con {max_count} mediciones.')
El día con la mayor cantidad de mediciones es: 2019-01-05 con 96 mediciones.

Extraer la media de cantidad de mediciones del mes y la cantidad máxima de mediciones de cada día del paciente 22 (1/2019)¶

In [47]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Agrupar los datos por día y contar el número de mediciones por día
daily_counts = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.date)['Measurement_time'].count()

# Encontrar el día con la mayor cantidad de mediciones
max_day = daily_counts.idxmax()

# Obtener la cantidad de mediciones para ese día
max_count = daily_counts.loc[max_day]
mean_count = daily_counts.mean()

print(f'La media de cantidad de mediciones por día es: {mean_count:.2f}')

# Crear un nuevo DataFrame con el conteo diario de mediciones
df_conteo_diario = pd.DataFrame({'Día': daily_counts.index, 'Cantidad de mediciones': daily_counts.values})

# Mostrar el resultado
print('\nConteo diario de mediciones:')
display(df_conteo_diario)
La media de cantidad de mediciones por día es: 53.97

Conteo diario de mediciones:
Día Cantidad de mediciones
0 2019-01-01 60
1 2019-01-02 75
2 2019-01-03 85
3 2019-01-04 93
4 2019-01-05 96
5 2019-01-06 76
6 2019-01-07 82
7 2019-01-08 82
8 2019-01-09 80
9 2019-01-10 24
10 2019-01-11 62
11 2019-01-12 50
12 2019-01-13 61
13 2019-01-15 61
14 2019-01-16 50
15 2019-01-17 16
16 2019-01-19 40
17 2019-01-20 66
18 2019-01-21 23
19 2019-01-22 7
20 2019-01-23 71
21 2019-01-24 3
22 2019-01-25 52
23 2019-01-26 12
24 2019-01-27 65
25 2019-01-28 61
26 2019-01-29 36
27 2019-01-30 10
28 2019-01-31 66

Del día con mayor cantidad de mediciones de glucosa tomadas, se muestra el tiempo de cada una de las mediciones tomadas y el tiempo transcurrido de una medición a la siguiente medición. Además se muestra la medición de la glucosa en relación con las mediciones tomadas del paciente 22 (5/1/2019)¶

In [48]:
# Filtrar los datos para el paciente 22 en el día 5 del mes 1 del año 2019
df_paciente_22_5_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1) & (df_paciente_22_1_2019['Measurement_date'].dt.day == 5)]

# Calcular las mediciones tomadas (Measurement_time) para ese día
day_measurements = df_paciente_22_5_1_2019['Measurement_time'].dt.time

# Calcular el tiempo transcurrido entre cada medición
time_diff = df_paciente_22_5_1_2019['Measurement_time'].diff()

# Crear un nuevo DataFrame con las mediciones tomadas y el tiempo transcurrido entre cada medición como columnas
result = pd.DataFrame({'Measurement_time': day_measurements, 'Time_diff': time_diff})

result['Measurement'] = df_paciente_22_5_1_2019['Measurement']

# Imprimir el resultado
print(f'Las mediciones tomadas el día 5 del mes 1 del año 2019 y el tiempo transcurrido entre cada medición son:')
result
Las mediciones tomadas el día 5 del mes 1 del año 2019 y el tiempo transcurrido entre cada medición son:
Out[48]:
Measurement_time Time_diff Measurement
Datetime
2019-01-05 00:00:00 00:00:00 NaT 216
2019-01-05 00:15:00 00:15:00 0 days 00:15:00 219
2019-01-05 00:30:00 00:30:00 0 days 00:15:00 223
2019-01-05 00:45:00 00:45:00 0 days 00:15:00 225
2019-01-05 01:00:00 01:00:00 0 days 00:15:00 190
... ... ... ...
2019-01-05 22:45:00 22:46:00 0 days 00:15:00 224
2019-01-05 23:00:00 23:01:00 0 days 00:15:00 222
2019-01-05 23:15:00 23:16:00 0 days 00:15:00 193
2019-01-05 23:30:00 23:31:00 0 days 00:15:00 190
2019-01-05 23:45:00 23:46:00 0 days 00:15:00 189

96 rows × 3 columns

Gráfica lineal que muestra la tendencia de la glucosa durante el día y tiene un sombreado que representa el tiempo en rango entre 70 mg/dl y 180 mg/dl en la fecha 5/1/2019 del paciente 22¶

Muestra desde inicio del día hasta final del día, y mostrando en horizontal (X) las horas del día separadas en 2 horas y mostrando en Vertical (Y) desde 40 hasta 500 que es lo que captura el freestlye libre 2. También en la gráfica lineal representar en puntos las mediciones de glucosa (Measurement_time) tomadas, y abajo se muestra una tabla con los valores de la glucosa representados en puntos en el gráfico.

In [49]:
import matplotlib.pyplot as plt
from matplotlib.table import Table

# Crear una figura y un eje para la gráfica
fig1, ax1 = plt.subplots(figsize=(15, 4))

# Convertir la columna 'Measurement_time' a formato datetime y extraer solo la hora
df_paciente_22_5_1_2019 = df_paciente_22_5_1_2019.copy() # quita advertencia
df_paciente_22_5_1_2019['hour'] = pd.to_datetime(df_paciente_22_5_1_2019['Measurement_time']).dt.hour


# Crear la gráfica lineal
ax1.plot(df_paciente_22_5_1_2019['hour'], df_paciente_22_5_1_2019['Measurement'])

# Agregar puntos que representen las mediciones de glucosa tomadas. "s" para minimizar los puntos
ax1.scatter(df_paciente_22_5_1_2019['hour'], df_paciente_22_5_1_2019['Measurement'], s=10, facecolors='none', edgecolors='b')

# Establecer los límites del eje x
ax1.set_xlim([0, 24])

# Establecer los límites del eje y
ax1.set_ylim([40, 500])

# Establecer las marcas del eje x
ax1.set_xticks(range(0, 25, 2))

# Establecer las etiquetas del eje x
ax1.set_xticklabels([f'{x}:00' for x in range(0, 25, 2)])

# Agregar una cuadrícula de líneas discontinuas al gráfico
ax1.grid(True, linestyle='dashed')

# Añadir un sombreado para el tiempo en rango de 70 a 180 en el eje y
ax1.axhspan(70, 180, facecolor='green', alpha=0.2)

# Agregar un título al gráfico
ax1.set_title('Día 5/10/2021') 

# Agregar etiquetas a los ejes x e y
ax1.set_xlabel('Horas')
ax1.set_ylabel('Nivel de Glucosa') 

# Crear una figura y un eje para la tabla
fig2, ax2 = plt.subplots()

# Crear una tabla que muestre los valores de glucosa para cada hora del día
table_data = []
for hour in range(0, 24, 2):
    measurements = df_paciente_22_5_1_2019[(df_paciente_22_5_1_2019['hour'] >= hour) & (df_paciente_22_5_1_2019['hour'] < hour + 2)]['Measurement'].values
    measurements_str = '\n'.join(map(str, measurements))
    table_data.append(measurements_str)

# Crear una tabla con 12 columnas en una posición específica
columns = ['0h - 2h', '2h - 4h', '4h - 6h', '6h - 8h', '8h - 10h', '10h - 12h', '12h - 14h', '14h - 16h', '16h - 18h', '18h - 20h', '20h - 22h', '22h - 24h']
table = Table(ax2, bbox=[0.059, -0.05 + 0.3, 1.9, 0.7])
#table = Table(ax2, bbox=[0.0, -0.05 + 0.3, 1.0, 0.15])

# Añadir las etiquetas de las columnas a la tabla
for i in range(len(columns)):
    table.add_cell(-1, i, width=0.15, height=0.1, text=columns[i], loc='center')

# Añadir los datos a la tabla
for i in range(len(table_data)):
    table.add_cell(0, i, width=0.15, height=0.9, text=table_data[i], loc='center')

ax2.add_table(table)

# Ocultar los ejes de la figura de la tabla
ax2.axis('off')

# Mostrar la gráfica y la tabla
plt.show()

Extraer paciente 22 en el mes 1, año 2019 las mediciones de glucosa según mañana, tarde y noche. Se analiza cuando la glucosa está en tiempo en rango¶

Mañana: 6:00 am y termina 12:00pm.

Tarde: 12:00 pm y termina 20h.

Noche: 20h y termina 6:00 am.

In [50]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df[(df['Patient_ID'] == 22) & (df['Measurement_date'].dt.year == 2019) & (df['Measurement_date'].dt.month == 1)]

# Extraer las mediciones de la mañana (entre las 6:00 am y las 12:00 pm)
morning_measurements = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 6) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 12)]['Measurement']

# Extraer las mediciones de la tarde (entre las 12:00 pm y las 8:00 pm)
afternoon_measurements = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 12) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 20)]['Measurement']

# Extraer las mediciones de la noche (entre las 8:00 pm y las 6:00 am del día siguiente)
night_measurements = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 20) | (df_paciente_22_1_2019['Measurement_time'].dt.hour < 6)]['Measurement']

# Calcular la media de las mediciones para cada período del día
morning_mean = morning_measurements.mean()
afternoon_mean = afternoon_measurements.mean()
night_mean = night_measurements.mean()

# Crear un nuevo DataFrame con los valores de las medias para cada período del día
df_medias = pd.DataFrame({'Período del día': ['Mañana (6am-12pm)', 'Tarde (12pm-8pm)', 'Noche (8pm-6am)'],
                          'Media': [f'{morning_mean:.2f}', f'{afternoon_mean:.2f}', f'{night_mean:.2f}']})
# Mostrar el resultado
print('Medias:')
display(df_medias)

# Mostrar el resultado
print('\nMediciones:')
# Crear un nuevo DataFrame con las mediciones de la mañana
df_morning = pd.DataFrame({'Mañana (6am-12pm)': morning_measurements})

# Crear un nuevo DataFrame con las mediciones de la tarde
df_afternoon = pd.DataFrame({'Tarde (12pm-8pm)': afternoon_measurements})

# Crear un nuevo DataFrame con las mediciones de la noche
df_night = pd.DataFrame({'Noche (8pm-6am)': night_measurements})

# Mostrar los resultados uno al lado del otro
from IPython.display import display_html

def display_side_by_side(*args):
    html_str=''
    for df in args:
        html_str+=df.to_html()
    display_html(html_str.replace('table','table style="display:inline"'),raw=True)

display_side_by_side(df_morning, df_afternoon, df_night)
Medias:
Período del día Media
0 Mañana (6am-12pm) 148.38
1 Tarde (12pm-8pm) 162.91
2 Noche (8pm-6am) 159.98
Mediciones:
Mañana (6am-12pm)
Datetime
2019-01-02 06:00:00 192
2019-01-02 06:15:00 188
2019-01-02 06:30:00 187
2019-01-02 06:45:00 183
2019-01-02 07:00:00 179
2019-01-02 07:15:00 178
2019-01-02 07:30:00 176
2019-01-02 07:45:00 174
2019-01-02 08:00:00 175
2019-01-03 06:00:00 132
2019-01-03 06:15:00 129
2019-01-03 06:30:00 128
2019-01-03 06:45:00 134
2019-01-03 07:00:00 136
2019-01-03 07:15:00 132
2019-01-03 07:30:00 126
2019-01-03 07:45:00 125
2019-01-03 08:00:00 132
2019-01-03 08:15:00 140
2019-01-03 08:30:00 143
2019-01-03 08:45:00 152
2019-01-03 09:00:00 156
2019-01-03 09:15:00 159
2019-01-03 09:30:00 166
2019-01-03 09:45:00 173
2019-01-03 10:00:00 173
2019-01-03 10:15:00 169
2019-01-03 10:30:00 161
2019-01-03 10:45:00 152
2019-01-03 11:00:00 144
2019-01-03 11:15:00 134
2019-01-03 11:30:00 124
2019-01-03 11:45:00 126
2019-01-04 06:00:00 166
2019-01-04 06:15:00 162
2019-01-04 06:30:00 162
2019-01-04 06:45:00 168
2019-01-04 07:00:00 171
2019-01-04 07:15:00 165
2019-01-04 07:30:00 155
2019-01-04 07:45:00 152
2019-01-04 08:00:00 152
2019-01-04 08:15:00 151
2019-01-04 08:30:00 147
2019-01-04 08:45:00 146
2019-01-04 09:00:00 147
2019-01-04 09:15:00 152
2019-01-04 09:30:00 155
2019-01-04 09:45:00 156
2019-01-04 10:00:00 160
2019-01-04 10:15:00 141
2019-01-04 10:30:00 136
2019-01-04 10:45:00 134
2019-01-04 11:00:00 134
2019-01-04 11:15:00 132
2019-01-04 11:30:00 125
2019-01-04 11:45:00 112
2019-01-05 06:00:00 146
2019-01-05 06:15:00 141
2019-01-05 06:30:00 137
2019-01-05 06:45:00 138
2019-01-05 07:00:00 135
2019-01-05 07:15:00 137
2019-01-05 07:30:00 136
2019-01-05 07:45:00 137
2019-01-05 08:00:00 138
2019-01-05 08:15:00 139
2019-01-05 08:30:00 138
2019-01-05 08:45:00 141
2019-01-05 09:00:00 141
2019-01-05 09:15:00 142
2019-01-05 09:30:00 146
2019-01-05 09:45:00 159
2019-01-05 10:00:00 166
2019-01-05 10:15:00 163
2019-01-05 10:30:00 158
2019-01-05 10:45:00 153
2019-01-05 11:00:00 148
2019-01-05 11:15:00 143
2019-01-05 11:30:00 145
2019-01-05 11:45:00 145
2019-01-06 06:00:00 152
2019-01-06 06:15:00 150
2019-01-06 06:30:00 149
2019-01-06 06:45:00 150
2019-01-06 07:00:00 160
2019-01-06 09:30:00 134
2019-01-06 09:45:00 127
2019-01-06 10:45:00 141
2019-01-06 11:00:00 147
2019-01-06 11:30:00 160
2019-01-07 06:00:00 143
2019-01-07 06:15:00 139
2019-01-07 06:30:00 138
2019-01-07 06:45:00 139
2019-01-07 07:00:00 146
2019-01-07 07:15:00 160
2019-01-07 07:30:00 173
2019-01-07 07:45:00 170
2019-01-07 08:00:00 170
2019-01-07 08:15:00 171
2019-01-07 08:30:00 170
2019-01-07 08:45:00 166
2019-01-07 09:00:00 163
2019-01-07 09:15:00 158
2019-01-07 09:30:00 155
2019-01-07 09:45:00 160
2019-01-07 10:00:00 169
2019-01-07 10:15:00 182
2019-01-07 10:30:00 184
2019-01-07 10:45:00 178
2019-01-07 11:00:00 171
2019-01-07 11:15:00 182
2019-01-08 06:00:00 162
2019-01-08 06:15:00 161
2019-01-08 06:30:00 160
2019-01-08 06:45:00 161
2019-01-08 07:00:00 160
2019-01-08 07:15:00 157
2019-01-08 07:30:00 158
2019-01-08 07:45:00 165
2019-01-08 08:00:00 166
2019-01-08 08:15:00 162
2019-01-08 08:30:00 155
2019-01-08 08:45:00 145
2019-01-08 09:00:00 138
2019-01-08 09:15:00 137
2019-01-08 09:30:00 134
2019-01-08 09:45:00 129
2019-01-08 10:00:00 136
2019-01-08 10:15:00 133
2019-01-08 10:30:00 144
2019-01-08 10:45:00 154
2019-01-08 11:00:00 164
2019-01-08 11:15:00 168
2019-01-08 11:30:00 165
2019-01-08 11:45:00 169
2019-01-09 06:15:00 165
2019-01-09 06:30:00 176
2019-01-09 07:15:00 191
2019-01-09 07:30:00 195
2019-01-09 07:45:00 196
2019-01-09 08:00:00 192
2019-01-09 08:15:00 192
2019-01-09 08:30:00 195
2019-01-09 08:45:00 195
2019-01-09 09:00:00 193
2019-01-09 09:15:00 194
2019-01-09 09:30:00 196
2019-01-09 09:45:00 198
2019-01-09 10:00:00 199
2019-01-09 10:15:00 195
2019-01-09 10:30:00 190
2019-01-09 10:45:00 186
2019-01-09 11:00:00 183
2019-01-09 11:15:00 180
2019-01-09 11:30:00 183
2019-01-09 11:45:00 188
2019-01-10 11:30:00 139
2019-01-10 11:45:00 162
2019-01-11 06:00:00 120
2019-01-11 06:15:00 120
2019-01-11 06:30:00 124
2019-01-11 06:45:00 129
2019-01-11 07:00:00 133
2019-01-11 07:15:00 126
2019-01-11 07:30:00 124
2019-01-11 07:45:00 136
2019-01-11 08:00:00 155
2019-01-11 08:15:00 168
2019-01-11 08:30:00 177
2019-01-11 08:45:00 179
2019-01-11 09:00:00 170
2019-01-11 09:15:00 156
2019-01-11 09:30:00 148
2019-01-11 09:45:00 148
2019-01-11 10:00:00 158
2019-01-11 10:15:00 172
2019-01-11 10:30:00 178
2019-01-11 10:45:00 178
2019-01-11 11:00:00 181
2019-01-11 11:15:00 226
2019-01-12 09:15:00 105
2019-01-12 09:30:00 106
2019-01-12 09:45:00 107
2019-01-12 10:00:00 109
2019-01-12 10:15:00 100
2019-01-12 10:30:00 98
2019-01-12 10:45:00 98
2019-01-12 11:00:00 106
2019-01-12 11:15:00 121
2019-01-12 11:30:00 144
2019-01-12 11:45:00 176
2019-01-13 08:30:00 134
2019-01-13 09:15:00 145
2019-01-13 09:30:00 146
2019-01-13 09:45:00 143
2019-01-13 10:00:00 148
2019-01-13 10:15:00 168
2019-01-13 10:30:00 181
2019-01-13 10:45:00 174
2019-01-13 11:30:00 151
2019-01-13 11:45:00 157
2019-01-15 06:30:00 112
2019-01-15 06:45:00 113
2019-01-15 07:00:00 105
2019-01-15 07:15:00 107
2019-01-15 07:30:00 104
2019-01-15 07:45:00 98
2019-01-15 08:00:00 97
2019-01-15 08:15:00 101
2019-01-15 08:30:00 109
2019-01-15 08:45:00 117
2019-01-15 09:00:00 130
2019-01-15 09:15:00 147
2019-01-15 09:30:00 149
2019-01-15 09:45:00 145
2019-01-15 10:00:00 145
2019-01-15 10:15:00 147
2019-01-15 10:30:00 148
2019-01-15 10:45:00 151
2019-01-15 11:00:00 152
2019-01-15 11:15:00 150
2019-01-15 11:30:00 145
2019-01-15 11:45:00 147
2019-01-16 11:30:00 146
2019-01-16 11:45:00 149
2019-01-19 06:00:00 108
2019-01-19 06:15:00 109
2019-01-19 06:30:00 109
2019-01-19 06:45:00 106
2019-01-19 07:00:00 103
2019-01-19 07:15:00 104
2019-01-19 07:30:00 108
2019-01-19 07:45:00 104
2019-01-19 08:00:00 95
2019-01-19 08:15:00 89
2019-01-19 08:30:00 90
2019-01-19 08:45:00 98
2019-01-19 09:00:00 111
2019-01-19 09:15:00 109
2019-01-19 10:45:00 143
2019-01-19 11:00:00 145
2019-01-20 06:00:00 181
2019-01-20 06:15:00 178
2019-01-20 06:30:00 184
2019-01-20 06:45:00 195
2019-01-20 07:00:00 196
2019-01-20 07:15:00 190
2019-01-20 07:30:00 187
2019-01-20 07:45:00 183
2019-01-20 08:00:00 177
2019-01-20 08:15:00 173
2019-01-20 08:30:00 171
2019-01-20 08:45:00 169
2019-01-20 09:00:00 170
2019-01-20 09:15:00 168
2019-01-20 09:30:00 164
2019-01-20 09:45:00 171
2019-01-20 10:00:00 173
2019-01-20 10:15:00 165
2019-01-20 10:30:00 162
2019-01-20 10:45:00 170
2019-01-20 11:15:00 185
2019-01-20 11:30:00 176
2019-01-20 11:45:00 165
2019-01-23 06:00:00 110
2019-01-23 06:15:00 109
2019-01-23 06:30:00 109
2019-01-23 06:45:00 114
2019-01-23 07:00:00 127
2019-01-23 07:15:00 135
2019-01-23 07:30:00 136
2019-01-23 07:45:00 138
2019-01-23 08:00:00 148
2019-01-23 08:15:00 155
2019-01-23 08:30:00 150
2019-01-23 08:45:00 145
2019-01-23 09:00:00 141
2019-01-23 09:15:00 136
2019-01-23 09:30:00 129
2019-01-23 09:45:00 126
2019-01-23 10:00:00 128
2019-01-23 10:15:00 128
2019-01-23 10:30:00 125
2019-01-23 10:45:00 131
2019-01-23 11:00:00 149
2019-01-23 11:15:00 167
2019-01-23 11:30:00 177
2019-01-23 11:45:00 189
2019-01-25 11:00:00 129
2019-01-25 11:15:00 133
2019-01-25 11:30:00 145
2019-01-25 11:45:00 163
2019-01-27 06:00:00 154
2019-01-27 06:15:00 157
2019-01-27 06:30:00 160
2019-01-27 06:45:00 150
2019-01-27 07:00:00 145
2019-01-27 07:15:00 147
2019-01-27 07:30:00 148
2019-01-27 07:45:00 145
2019-01-27 08:00:00 152
2019-01-27 08:15:00 163
2019-01-27 08:30:00 169
2019-01-27 08:45:00 169
2019-01-27 09:00:00 168
2019-01-27 09:15:00 160
2019-01-27 09:30:00 154
2019-01-27 09:45:00 146
2019-01-27 10:00:00 137
2019-01-27 10:15:00 127
2019-01-27 10:30:00 121
2019-01-27 10:45:00 138
2019-01-27 11:00:00 150
2019-01-27 11:15:00 177
2019-01-27 11:30:00 206
2019-01-27 11:45:00 222
2019-01-28 06:00:00 79
2019-01-28 06:15:00 79
2019-01-28 06:30:00 80
2019-01-28 06:45:00 85
2019-01-28 07:00:00 88
2019-01-28 07:15:00 97
2019-01-28 07:30:00 104
2019-01-28 07:45:00 103
2019-01-29 06:00:00 138
2019-01-29 06:45:00 146
2019-01-29 07:15:00 138
2019-01-29 07:30:00 139
2019-01-29 07:45:00 141
2019-01-29 08:00:00 143
2019-01-29 08:15:00 133
2019-01-29 08:30:00 119
2019-01-29 09:15:00 95
2019-01-29 09:30:00 104
2019-01-29 09:45:00 118
2019-01-29 10:00:00 130
2019-01-29 10:15:00 140
2019-01-29 10:30:00 152
2019-01-31 06:00:00 121
2019-01-31 06:15:00 120
2019-01-31 06:30:00 116
2019-01-31 06:45:00 122
2019-01-31 07:00:00 156
2019-01-31 07:15:00 161
2019-01-31 07:30:00 167
2019-01-31 08:30:00 202
2019-01-31 08:45:00 195
2019-01-31 09:00:00 187
2019-01-31 10:00:00 148
2019-01-31 10:15:00 141
2019-01-31 10:30:00 131
2019-01-31 10:45:00 119
2019-01-31 11:30:00 127
Tarde (12pm-8pm)
Datetime
2019-01-01 12:30:00 153
2019-01-01 12:45:00 144
2019-01-01 13:00:00 143
2019-01-01 13:15:00 148
2019-01-01 13:30:00 144
2019-01-01 13:45:00 140
2019-01-01 14:00:00 139
2019-01-01 14:15:00 137
2019-01-01 14:30:00 132
2019-01-01 14:45:00 137
2019-01-01 15:00:00 149
2019-01-01 15:15:00 160
2019-01-01 15:30:00 158
2019-01-01 15:45:00 146
2019-01-01 16:00:00 142
2019-01-01 16:15:00 147
2019-01-01 16:30:00 146
2019-01-01 16:45:00 148
2019-01-01 17:00:00 151
2019-01-01 17:15:00 147
2019-01-01 17:30:00 145
2019-01-01 17:45:00 124
2019-01-01 18:00:00 125
2019-01-01 18:15:00 127
2019-01-01 18:30:00 130
2019-01-01 18:45:00 132
2019-01-01 19:00:00 131
2019-01-01 19:15:00 128
2019-01-01 19:30:00 123
2019-01-01 19:45:00 117
2019-01-02 13:15:00 130
2019-01-02 13:30:00 124
2019-01-02 13:45:00 120
2019-01-02 14:00:00 115
2019-01-02 14:15:00 120
2019-01-02 14:30:00 131
2019-01-02 14:45:00 140
2019-01-02 15:00:00 142
2019-01-02 15:15:00 143
2019-01-02 15:30:00 146
2019-01-02 15:45:00 152
2019-01-02 16:00:00 153
2019-01-02 16:15:00 153
2019-01-02 16:30:00 158
2019-01-02 16:45:00 162
2019-01-02 17:00:00 175
2019-01-02 17:15:00 184
2019-01-02 17:30:00 190
2019-01-02 17:45:00 191
2019-01-02 18:00:00 188
2019-01-02 18:15:00 181
2019-01-02 18:30:00 177
2019-01-02 18:45:00 167
2019-01-02 19:00:00 157
2019-01-02 19:15:00 155
2019-01-02 19:30:00 155
2019-01-02 19:45:00 155
2019-01-03 12:00:00 136
2019-01-03 12:15:00 140
2019-01-03 12:30:00 147
2019-01-03 12:45:00 157
2019-01-03 13:00:00 163
2019-01-03 13:15:00 162
2019-01-03 13:30:00 163
2019-01-03 13:45:00 159
2019-01-03 14:00:00 141
2019-01-03 14:15:00 131
2019-01-03 14:30:00 124
2019-01-03 14:45:00 122
2019-01-03 15:00:00 124
2019-01-03 15:15:00 127
2019-01-03 15:30:00 138
2019-01-03 15:45:00 149
2019-01-03 16:00:00 168
2019-01-03 16:15:00 165
2019-01-03 16:30:00 158
2019-01-03 16:45:00 154
2019-01-03 17:00:00 154
2019-01-03 17:15:00 157
2019-01-03 17:30:00 158
2019-01-03 17:45:00 153
2019-01-03 18:00:00 148
2019-01-03 18:15:00 147
2019-01-03 18:30:00 152
2019-01-03 18:45:00 156
2019-01-03 19:00:00 162
2019-01-03 19:15:00 168
2019-01-03 19:30:00 171
2019-01-03 19:45:00 174
2019-01-04 12:00:00 108
2019-01-04 12:15:00 109
2019-01-04 12:30:00 111
2019-01-04 12:45:00 110
2019-01-04 13:00:00 108
2019-01-04 13:15:00 130
2019-01-04 13:30:00 128
2019-01-04 13:45:00 124
2019-01-04 14:00:00 120
2019-01-04 14:15:00 119
2019-01-04 14:30:00 121
2019-01-04 14:45:00 129
2019-01-04 15:00:00 148
2019-01-04 15:15:00 161
2019-01-04 15:30:00 164
2019-01-04 15:45:00 165
2019-01-04 16:00:00 175
2019-01-04 16:15:00 188
2019-01-04 16:30:00 200
2019-01-04 16:45:00 198
2019-01-04 17:00:00 200
2019-01-04 17:15:00 195
2019-01-04 17:30:00 202
2019-01-04 17:45:00 215
2019-01-04 18:00:00 218
2019-01-04 18:15:00 214
2019-01-04 18:30:00 213
2019-01-04 18:45:00 213
2019-01-04 19:00:00 192
2019-01-04 19:15:00 188
2019-01-04 19:30:00 178
2019-01-04 19:45:00 174
2019-01-05 12:00:00 142
2019-01-05 12:15:00 140
2019-01-05 12:30:00 143
2019-01-05 12:45:00 146
2019-01-05 13:00:00 154
2019-01-05 13:15:00 160
2019-01-05 13:30:00 158
2019-01-05 13:45:00 149
2019-01-05 14:00:00 138
2019-01-05 14:15:00 131
2019-01-05 14:30:00 125
2019-01-05 14:45:00 106
2019-01-05 15:00:00 124
2019-01-05 15:15:00 145
2019-01-05 15:30:00 161
2019-01-05 15:45:00 162
2019-01-05 16:00:00 168
2019-01-05 16:15:00 172
2019-01-05 16:30:00 173
2019-01-05 16:45:00 174
2019-01-05 17:00:00 174
2019-01-05 17:15:00 167
2019-01-05 17:30:00 159
2019-01-05 17:45:00 157
2019-01-05 18:00:00 152
2019-01-05 18:15:00 148
2019-01-05 18:30:00 149
2019-01-05 18:45:00 153
2019-01-05 19:00:00 158
2019-01-05 19:15:00 162
2019-01-05 19:30:00 193
2019-01-05 19:45:00 195
2019-01-06 12:15:00 120
2019-01-06 12:30:00 118
2019-01-06 14:00:00 128
2019-01-06 14:15:00 146
2019-01-06 14:30:00 156
2019-01-06 14:45:00 166
2019-01-06 15:00:00 185
2019-01-06 15:15:00 203
2019-01-06 15:30:00 262
2019-01-06 15:45:00 219
2019-01-06 16:00:00 218
2019-01-06 16:15:00 226
2019-01-06 16:30:00 235
2019-01-06 16:45:00 237
2019-01-06 17:00:00 237
2019-01-06 17:15:00 232
2019-01-06 17:30:00 226
2019-01-06 17:45:00 224
2019-01-06 18:00:00 228
2019-01-06 18:15:00 236
2019-01-06 18:30:00 244
2019-01-06 18:45:00 246
2019-01-06 19:00:00 249
2019-01-06 19:15:00 254
2019-01-06 19:30:00 255
2019-01-06 19:45:00 252
2019-01-07 13:30:00 140
2019-01-07 13:45:00 137
2019-01-07 14:00:00 133
2019-01-07 14:15:00 136
2019-01-07 15:15:00 165
2019-01-07 15:30:00 170
2019-01-07 15:45:00 169
2019-01-07 16:00:00 164
2019-01-07 17:00:00 136
2019-01-07 17:15:00 150
2019-01-07 17:30:00 157
2019-01-07 17:45:00 161
2019-01-07 18:00:00 167
2019-01-07 18:15:00 172
2019-01-07 18:30:00 179
2019-01-07 18:45:00 188
2019-01-07 19:00:00 191
2019-01-07 19:15:00 184
2019-01-07 19:30:00 181
2019-01-07 19:45:00 181
2019-01-08 12:00:00 175
2019-01-08 12:15:00 200
2019-01-08 12:30:00 186
2019-01-08 13:15:00 173
2019-01-08 13:30:00 167
2019-01-08 14:15:00 148
2019-01-08 14:45:00 134
2019-01-08 15:30:00 198
2019-01-08 15:45:00 199
2019-01-08 16:30:00 189
2019-01-08 16:45:00 189
2019-01-08 17:30:00 142
2019-01-08 17:45:00 133
2019-01-08 18:00:00 128
2019-01-08 18:15:00 126
2019-01-08 18:30:00 130
2019-01-08 18:45:00 136
2019-01-08 19:00:00 142
2019-01-08 19:15:00 146
2019-01-09 12:00:00 192
2019-01-09 12:15:00 200
2019-01-09 12:30:00 214
2019-01-09 12:45:00 218
2019-01-09 13:00:00 209
2019-01-09 13:15:00 200
2019-01-09 13:30:00 192
2019-01-09 13:45:00 182
2019-01-09 14:00:00 176
2019-01-09 14:15:00 181
2019-01-09 14:30:00 190
2019-01-09 14:45:00 196
2019-01-09 15:00:00 209
2019-01-09 15:15:00 204
2019-01-09 15:30:00 209
2019-01-09 15:45:00 211
2019-01-09 16:30:00 181
2019-01-09 16:45:00 171
2019-01-09 17:30:00 147
2019-01-09 17:45:00 150
2019-01-09 18:15:00 168
2019-01-09 19:15:00 216
2019-01-09 19:45:00 214
2019-01-10 12:00:00 170
2019-01-10 12:15:00 180
2019-01-10 12:30:00 183
2019-01-10 12:45:00 178
2019-01-10 13:00:00 174
2019-01-10 13:15:00 173
2019-01-10 13:30:00 173
2019-01-10 13:45:00 169
2019-01-10 14:00:00 184
2019-01-10 14:15:00 153
2019-01-10 14:45:00 140
2019-01-11 18:00:00 151
2019-01-11 18:15:00 160
2019-01-11 18:30:00 160
2019-01-11 18:45:00 153
2019-01-12 12:00:00 195
2019-01-12 12:15:00 192
2019-01-12 12:30:00 184
2019-01-12 12:45:00 172
2019-01-12 13:00:00 158
2019-01-12 13:15:00 148
2019-01-12 13:30:00 142
2019-01-12 13:45:00 137
2019-01-12 14:00:00 132
2019-01-12 14:15:00 118
2019-01-12 14:30:00 108
2019-01-12 14:45:00 112
2019-01-12 15:00:00 124
2019-01-12 15:15:00 130
2019-01-12 15:30:00 143
2019-01-12 15:45:00 174
2019-01-12 16:00:00 201
2019-01-12 16:15:00 205
2019-01-12 16:30:00 206
2019-01-12 16:45:00 206
2019-01-12 17:00:00 201
2019-01-12 17:15:00 191
2019-01-12 17:30:00 182
2019-01-12 17:45:00 175
2019-01-12 18:00:00 170
2019-01-12 18:15:00 166
2019-01-12 18:30:00 169
2019-01-12 18:45:00 180
2019-01-12 19:00:00 195
2019-01-13 12:30:00 159
2019-01-13 12:45:00 156
2019-01-13 13:00:00 150
2019-01-13 13:15:00 148
2019-01-13 13:30:00 151
2019-01-13 13:45:00 160
2019-01-13 14:00:00 173
2019-01-13 14:15:00 184
2019-01-13 14:30:00 188
2019-01-13 14:45:00 196
2019-01-13 15:00:00 207
2019-01-13 15:15:00 216
2019-01-13 15:30:00 223
2019-01-13 15:45:00 230
2019-01-13 16:00:00 238
2019-01-13 16:15:00 241
2019-01-13 16:30:00 242
2019-01-13 16:45:00 237
2019-01-13 17:00:00 228
2019-01-13 17:15:00 221
2019-01-13 17:30:00 213
2019-01-13 17:45:00 205
2019-01-13 18:00:00 199
2019-01-13 18:15:00 193
2019-01-13 18:30:00 185
2019-01-13 18:45:00 173
2019-01-13 19:00:00 165
2019-01-13 19:15:00 161
2019-01-13 19:30:00 152
2019-01-13 19:45:00 146
2019-01-15 12:00:00 155
2019-01-15 12:15:00 160
2019-01-15 12:30:00 164
2019-01-15 12:45:00 164
2019-01-15 13:00:00 157
2019-01-15 13:15:00 154
2019-01-15 13:30:00 154
2019-01-15 13:45:00 151
2019-01-15 14:00:00 144
2019-01-15 14:30:00 167
2019-01-15 14:45:00 152
2019-01-15 16:00:00 152
2019-01-15 16:30:00 128
2019-01-15 16:45:00 123
2019-01-15 17:00:00 116
2019-01-15 17:15:00 111
2019-01-15 17:30:00 105
2019-01-15 17:45:00 100
2019-01-15 18:00:00 101
2019-01-15 18:15:00 105
2019-01-15 18:30:00 105
2019-01-15 18:45:00 104
2019-01-15 19:00:00 104
2019-01-15 19:15:00 102
2019-01-15 19:30:00 101
2019-01-15 19:45:00 103
2019-01-16 12:00:00 154
2019-01-16 12:15:00 154
2019-01-16 12:30:00 154
2019-01-16 12:45:00 167
2019-01-16 13:00:00 173
2019-01-16 13:15:00 176
2019-01-16 13:30:00 188
2019-01-16 13:45:00 183
2019-01-16 14:00:00 170
2019-01-16 14:15:00 164
2019-01-16 14:30:00 138
2019-01-16 14:45:00 133
2019-01-16 15:00:00 132
2019-01-16 15:15:00 139
2019-01-16 15:30:00 143
2019-01-16 15:45:00 136
2019-01-16 16:00:00 126
2019-01-16 16:15:00 116
2019-01-16 16:30:00 108
2019-01-16 16:45:00 103
2019-01-16 17:00:00 102
2019-01-16 17:15:00 104
2019-01-16 17:30:00 106
2019-01-16 17:45:00 112
2019-01-16 18:00:00 119
2019-01-16 18:15:00 128
2019-01-16 18:30:00 142
2019-01-16 18:45:00 153
2019-01-16 19:00:00 158
2019-01-16 19:15:00 158
2019-01-16 19:30:00 156
2019-01-16 19:45:00 160
2019-01-19 12:30:00 176
2019-01-19 12:45:00 172
2019-01-19 13:15:00 161
2019-01-19 13:30:00 149
2019-01-19 13:45:00 140
2019-01-20 12:00:00 160
2019-01-20 12:15:00 158
2019-01-20 12:30:00 152
2019-01-20 12:45:00 145
2019-01-20 13:00:00 139
2019-01-20 13:15:00 136
2019-01-20 13:30:00 135
2019-01-20 13:45:00 138
2019-01-20 14:00:00 138
2019-01-20 14:15:00 132
2019-01-20 14:30:00 120
2019-01-20 14:45:00 116
2019-01-20 16:15:00 144
2019-01-20 16:30:00 166
2019-01-20 16:45:00 171
2019-01-20 17:00:00 174
2019-01-20 17:15:00 178
2019-01-20 17:30:00 184
2019-01-20 17:45:00 187
2019-01-20 18:00:00 188
2019-01-20 18:15:00 194
2019-01-20 18:30:00 207
2019-01-20 18:45:00 223
2019-01-20 19:00:00 232
2019-01-20 19:15:00 228
2019-01-20 19:30:00 223
2019-01-20 19:45:00 217
2019-01-21 17:30:00 190
2019-01-21 17:45:00 186
2019-01-21 18:00:00 183
2019-01-21 18:15:00 191
2019-01-21 18:30:00 197
2019-01-21 18:45:00 202
2019-01-21 19:00:00 204
2019-01-21 19:15:00 182
2019-01-21 19:30:00 182
2019-01-21 19:45:00 176
2019-01-23 12:00:00 199
2019-01-23 12:15:00 202
2019-01-23 12:30:00 201
2019-01-23 12:45:00 195
2019-01-23 13:00:00 184
2019-01-23 13:15:00 175
2019-01-23 13:30:00 175
2019-01-23 13:45:00 174
2019-01-23 14:00:00 170
2019-01-23 14:15:00 171
2019-01-23 14:30:00 172
2019-01-23 14:45:00 177
2019-01-23 15:00:00 169
2019-01-23 15:15:00 173
2019-01-23 15:30:00 173
2019-01-23 15:45:00 174
2019-01-23 16:00:00 171
2019-01-23 16:15:00 160
2019-01-23 16:30:00 149
2019-01-23 16:45:00 138
2019-01-23 17:00:00 132
2019-01-25 12:00:00 180
2019-01-25 12:15:00 198
2019-01-25 12:30:00 210
2019-01-25 12:45:00 209
2019-01-25 13:00:00 203
2019-01-25 13:15:00 201
2019-01-25 13:30:00 201
2019-01-25 13:45:00 205
2019-01-25 14:00:00 207
2019-01-25 14:15:00 200
2019-01-25 14:30:00 193
2019-01-25 14:45:00 192
2019-01-25 15:00:00 191
2019-01-25 15:15:00 188
2019-01-25 15:30:00 182
2019-01-25 15:45:00 173
2019-01-25 16:00:00 161
2019-01-25 16:15:00 141
2019-01-25 16:30:00 114
2019-01-25 16:45:00 94
2019-01-25 17:00:00 86
2019-01-25 17:15:00 80
2019-01-25 17:30:00 82
2019-01-25 17:45:00 91
2019-01-25 18:00:00 97
2019-01-25 18:15:00 103
2019-01-25 18:30:00 115
2019-01-25 18:45:00 133
2019-01-25 19:00:00 144
2019-01-25 19:15:00 148
2019-01-25 19:30:00 148
2019-01-25 19:45:00 151
2019-01-27 12:00:00 229
2019-01-27 12:15:00 236
2019-01-27 12:30:00 231
2019-01-27 12:45:00 220
2019-01-27 13:00:00 210
2019-01-27 13:15:00 199
2019-01-27 13:30:00 184
2019-01-27 13:45:00 169
2019-01-27 14:00:00 157
2019-01-27 14:15:00 148
2019-01-27 14:30:00 141
2019-01-27 14:45:00 140
2019-01-28 14:45:00 175
2019-01-28 15:00:00 172
2019-01-28 15:15:00 166
2019-01-28 15:45:00 162
2019-01-28 16:00:00 158
2019-01-28 16:30:00 138
2019-01-28 16:45:00 124
2019-01-28 17:00:00 114
2019-01-28 17:15:00 114
2019-01-28 17:30:00 116
2019-01-28 17:45:00 113
2019-01-28 18:00:00 114
2019-01-28 18:15:00 120
2019-01-28 18:30:00 127
2019-01-28 18:45:00 132
2019-01-28 19:00:00 146
2019-01-28 19:15:00 167
2019-01-28 19:30:00 169
2019-01-28 19:45:00 162
2019-01-31 12:00:00 135
2019-01-31 12:15:00 135
2019-01-31 12:30:00 135
2019-01-31 12:45:00 138
2019-01-31 13:00:00 142
2019-01-31 13:15:00 139
2019-01-31 13:30:00 135
2019-01-31 13:45:00 132
2019-01-31 14:00:00 134
2019-01-31 14:45:00 180
2019-01-31 15:30:00 175
2019-01-31 15:45:00 176
2019-01-31 16:00:00 189
2019-01-31 16:15:00 192
2019-01-31 19:45:00 145
Noche (8pm-6am)
Datetime
2019-01-01 00:00:00 194
2019-01-01 00:15:00 205
2019-01-01 00:30:00 216
2019-01-01 00:45:00 217
2019-01-01 01:00:00 225
2019-01-01 01:15:00 233
2019-01-01 01:30:00 236
2019-01-01 01:45:00 231
2019-01-01 02:00:00 230
2019-01-01 02:15:00 226
2019-01-01 02:30:00 217
2019-01-01 02:45:00 214
2019-01-01 03:00:00 216
2019-01-01 03:15:00 215
2019-01-01 03:30:00 210
2019-01-01 03:45:00 185
2019-01-01 20:00:00 116
2019-01-01 20:15:00 121
2019-01-01 20:30:00 128
2019-01-01 20:45:00 135
2019-01-01 21:00:00 138
2019-01-01 21:15:00 136
2019-01-01 21:30:00 132
2019-01-01 21:45:00 133
2019-01-01 22:00:00 128
2019-01-01 22:15:00 110
2019-01-01 22:30:00 116
2019-01-01 23:15:00 111
2019-01-01 23:30:00 109
2019-01-01 23:45:00 107
2019-01-02 00:00:00 107
2019-01-02 00:15:00 113
2019-01-02 00:30:00 124
2019-01-02 01:00:00 131
2019-01-02 01:15:00 117
2019-01-02 01:30:00 132
2019-01-02 01:45:00 130
2019-01-02 02:00:00 133
2019-01-02 02:15:00 135
2019-01-02 02:30:00 137
2019-01-02 02:45:00 138
2019-01-02 03:00:00 139
2019-01-02 03:15:00 155
2019-01-02 03:30:00 182
2019-01-02 03:45:00 190
2019-01-02 04:00:00 195
2019-01-02 04:15:00 201
2019-01-02 04:30:00 202
2019-01-02 04:45:00 195
2019-01-02 05:00:00 192
2019-01-02 05:15:00 197
2019-01-02 05:30:00 199
2019-01-02 05:45:00 198
2019-01-02 20:00:00 157
2019-01-02 20:15:00 158
2019-01-02 20:30:00 156
2019-01-02 20:45:00 155
2019-01-02 21:00:00 153
2019-01-02 21:15:00 152
2019-01-02 21:30:00 154
2019-01-02 21:45:00 156
2019-01-02 22:00:00 154
2019-01-02 22:15:00 152
2019-01-02 22:30:00 96
2019-01-02 22:45:00 95
2019-01-02 23:00:00 96
2019-01-02 23:15:00 101
2019-01-02 23:30:00 100
2019-01-02 23:45:00 93
2019-01-03 02:30:00 142
2019-01-03 02:45:00 138
2019-01-03 03:00:00 142
2019-01-03 03:15:00 142
2019-01-03 03:30:00 146
2019-01-03 03:45:00 153
2019-01-03 04:00:00 153
2019-01-03 04:15:00 151
2019-01-03 04:30:00 146
2019-01-03 04:45:00 142
2019-01-03 05:00:00 139
2019-01-03 05:15:00 136
2019-01-03 05:30:00 135
2019-01-03 05:45:00 134
2019-01-03 20:00:00 178
2019-01-03 20:15:00 180
2019-01-03 20:30:00 180
2019-01-03 20:45:00 178
2019-01-03 21:00:00 169
2019-01-03 21:15:00 161
2019-01-03 21:30:00 155
2019-01-03 21:45:00 148
2019-01-03 22:00:00 144
2019-01-03 22:15:00 143
2019-01-03 22:30:00 144
2019-01-03 22:45:00 132
2019-01-03 23:00:00 154
2019-01-03 23:15:00 145
2019-01-03 23:30:00 127
2019-01-04 00:45:00 130
2019-01-04 01:00:00 140
2019-01-04 01:15:00 159
2019-01-04 01:30:00 176
2019-01-04 01:45:00 180
2019-01-04 02:00:00 175
2019-01-04 02:15:00 176
2019-01-04 02:30:00 200
2019-01-04 02:45:00 200
2019-01-04 03:00:00 200
2019-01-04 03:15:00 202
2019-01-04 03:30:00 181
2019-01-04 03:45:00 178
2019-01-04 04:00:00 176
2019-01-04 04:15:00 175
2019-01-04 04:30:00 175
2019-01-04 04:45:00 172
2019-01-04 05:00:00 170
2019-01-04 05:15:00 169
2019-01-04 05:30:00 165
2019-01-04 05:45:00 163
2019-01-04 20:00:00 174
2019-01-04 20:15:00 173
2019-01-04 20:30:00 175
2019-01-04 20:45:00 181
2019-01-04 21:00:00 192
2019-01-04 21:15:00 199
2019-01-04 21:30:00 203
2019-01-04 21:45:00 210
2019-01-04 22:00:00 229
2019-01-04 22:15:00 229
2019-01-04 22:30:00 226
2019-01-04 22:45:00 228
2019-01-04 23:00:00 241
2019-01-04 23:15:00 231
2019-01-04 23:30:00 205
2019-01-04 23:45:00 210
2019-01-05 00:00:00 216
2019-01-05 00:15:00 219
2019-01-05 00:30:00 223
2019-01-05 00:45:00 225
2019-01-05 01:00:00 190
2019-01-05 01:15:00 185
2019-01-05 01:30:00 185
2019-01-05 01:45:00 185
2019-01-05 02:00:00 179
2019-01-05 02:15:00 169
2019-01-05 02:30:00 161
2019-01-05 02:45:00 152
2019-01-05 03:00:00 144
2019-01-05 03:15:00 142
2019-01-05 03:30:00 143
2019-01-05 03:45:00 146
2019-01-05 04:00:00 140
2019-01-05 04:15:00 169
2019-01-05 04:30:00 164
2019-01-05 04:45:00 161
2019-01-05 05:00:00 160
2019-01-05 05:15:00 156
2019-01-05 05:30:00 152
2019-01-05 05:45:00 148
2019-01-05 20:00:00 192
2019-01-05 20:15:00 191
2019-01-05 20:30:00 195
2019-01-05 20:45:00 199
2019-01-05 21:00:00 199
2019-01-05 21:15:00 203
2019-01-05 21:30:00 215
2019-01-05 21:45:00 181
2019-01-05 22:00:00 202
2019-01-05 22:15:00 213
2019-01-05 22:30:00 211
2019-01-05 22:45:00 224
2019-01-05 23:00:00 222
2019-01-05 23:15:00 193
2019-01-05 23:30:00 190
2019-01-05 23:45:00 189
2019-01-06 00:00:00 193
2019-01-06 00:15:00 198
2019-01-06 00:30:00 193
2019-01-06 00:45:00 189
2019-01-06 01:00:00 192
2019-01-06 01:15:00 192
2019-01-06 01:30:00 206
2019-01-06 01:45:00 198
2019-01-06 02:00:00 186
2019-01-06 02:15:00 180
2019-01-06 02:30:00 177
2019-01-06 02:45:00 172
2019-01-06 03:00:00 166
2019-01-06 03:15:00 157
2019-01-06 03:30:00 151
2019-01-06 03:45:00 149
2019-01-06 04:00:00 148
2019-01-06 04:15:00 149
2019-01-06 04:30:00 149
2019-01-06 04:45:00 150
2019-01-06 05:00:00 150
2019-01-06 05:15:00 152
2019-01-06 05:30:00 154
2019-01-06 05:45:00 154
2019-01-06 20:00:00 247
2019-01-06 20:15:00 238
2019-01-06 20:30:00 228
2019-01-06 20:45:00 216
2019-01-06 21:00:00 197
2019-01-06 21:15:00 184
2019-01-06 21:30:00 160
2019-01-06 21:45:00 159
2019-01-06 22:00:00 154
2019-01-06 22:15:00 142
2019-01-06 22:30:00 123
2019-01-06 22:45:00 110
2019-01-06 23:00:00 106
2019-01-06 23:15:00 105
2019-01-06 23:30:00 93
2019-01-06 23:45:00 86
2019-01-07 00:00:00 80
2019-01-07 00:15:00 80
2019-01-07 00:30:00 83
2019-01-07 00:45:00 80
2019-01-07 01:00:00 74
2019-01-07 01:15:00 72
2019-01-07 01:30:00 73
2019-01-07 01:45:00 79
2019-01-07 02:00:00 118
2019-01-07 02:15:00 119
2019-01-07 02:30:00 120
2019-01-07 02:45:00 122
2019-01-07 03:00:00 133
2019-01-07 03:15:00 133
2019-01-07 03:30:00 134
2019-01-07 03:45:00 138
2019-01-07 04:00:00 146
2019-01-07 04:15:00 151
2019-01-07 04:30:00 150
2019-01-07 04:45:00 150
2019-01-07 05:00:00 148
2019-01-07 05:15:00 144
2019-01-07 05:30:00 142
2019-01-07 05:45:00 143
2019-01-07 20:00:00 180
2019-01-07 20:15:00 180
2019-01-07 20:30:00 175
2019-01-07 20:45:00 167
2019-01-07 21:00:00 158
2019-01-07 21:15:00 150
2019-01-07 21:30:00 145
2019-01-07 21:45:00 145
2019-01-07 22:00:00 146
2019-01-07 22:15:00 154
2019-01-07 22:30:00 147
2019-01-07 22:45:00 140
2019-01-07 23:00:00 136
2019-01-07 23:15:00 132
2019-01-07 23:30:00 129
2019-01-07 23:45:00 127
2019-01-08 00:00:00 124
2019-01-08 00:15:00 122
2019-01-08 00:30:00 120
2019-01-08 00:45:00 121
2019-01-08 01:00:00 121
2019-01-08 01:15:00 125
2019-01-08 01:30:00 134
2019-01-08 01:45:00 138
2019-01-08 02:00:00 143
2019-01-08 02:15:00 146
2019-01-08 02:30:00 147
2019-01-08 02:45:00 151
2019-01-08 03:00:00 206
2019-01-08 03:15:00 203
2019-01-08 03:30:00 201
2019-01-08 03:45:00 198
2019-01-08 04:00:00 179
2019-01-08 04:15:00 175
2019-01-08 04:30:00 171
2019-01-08 04:45:00 170
2019-01-08 05:00:00 171
2019-01-08 05:15:00 173
2019-01-08 05:30:00 173
2019-01-08 05:45:00 168
2019-01-08 20:15:00 141
2019-01-08 20:30:00 132
2019-01-08 20:45:00 124
2019-01-08 21:00:00 120
2019-01-08 21:15:00 123
2019-01-08 21:30:00 124
2019-01-08 21:45:00 132
2019-01-08 22:00:00 137
2019-01-08 22:15:00 145
2019-01-08 22:30:00 146
2019-01-08 22:45:00 148
2019-01-08 23:00:00 148
2019-01-08 23:15:00 138
2019-01-08 23:30:00 139
2019-01-08 23:45:00 156
2019-01-09 00:00:00 159
2019-01-09 00:15:00 160
2019-01-09 00:30:00 160
2019-01-09 00:45:00 158
2019-01-09 01:00:00 157
2019-01-09 01:15:00 162
2019-01-09 01:30:00 171
2019-01-09 01:45:00 181
2019-01-09 02:00:00 182
2019-01-09 02:15:00 163
2019-01-09 02:30:00 155
2019-01-09 02:45:00 149
2019-01-09 03:00:00 147
2019-01-09 03:15:00 148
2019-01-09 03:30:00 145
2019-01-09 03:45:00 145
2019-01-09 04:00:00 148
2019-01-09 04:45:00 159
2019-01-09 05:15:00 156
2019-01-09 05:30:00 157
2019-01-09 20:00:00 209
2019-01-09 20:15:00 207
2019-01-09 20:30:00 207
2019-01-09 20:45:00 203
2019-01-09 21:00:00 195
2019-01-09 21:15:00 183
2019-01-09 21:30:00 164
2019-01-09 21:45:00 148
2019-01-09 22:00:00 137
2019-01-09 22:15:00 127
2019-01-09 22:30:00 116
2019-01-09 22:45:00 112
2019-01-09 23:00:00 118
2019-01-09 23:15:00 141
2019-01-09 23:30:00 144
2019-01-09 23:45:00 164
2019-01-10 00:00:00 167
2019-01-10 00:15:00 152
2019-01-10 02:30:00 161
2019-01-10 03:00:00 168
2019-01-10 03:15:00 166
2019-01-10 03:30:00 161
2019-01-10 03:45:00 155
2019-01-10 23:00:00 153
2019-01-10 23:15:00 168
2019-01-10 23:30:00 178
2019-01-10 23:45:00 178
2019-01-11 00:00:00 179
2019-01-11 00:15:00 178
2019-01-11 00:30:00 171
2019-01-11 00:45:00 168
2019-01-11 01:00:00 162
2019-01-11 01:15:00 153
2019-01-11 01:30:00 147
2019-01-11 01:45:00 143
2019-01-11 02:00:00 142
2019-01-11 02:15:00 140
2019-01-11 02:30:00 138
2019-01-11 02:45:00 136
2019-01-11 03:00:00 134
2019-01-11 03:15:00 132
2019-01-11 03:30:00 131
2019-01-11 03:45:00 130
2019-01-11 04:00:00 129
2019-01-11 04:15:00 132
2019-01-11 04:30:00 131
2019-01-11 04:45:00 130
2019-01-11 05:00:00 128
2019-01-11 05:15:00 126
2019-01-11 05:30:00 126
2019-01-11 05:45:00 123
2019-01-11 20:15:00 140
2019-01-11 20:30:00 156
2019-01-11 20:45:00 164
2019-01-11 21:00:00 179
2019-01-11 21:15:00 185
2019-01-11 22:15:00 298
2019-01-11 22:30:00 308
2019-01-11 22:45:00 312
2019-01-11 23:00:00 315
2019-01-11 23:15:00 331
2019-01-11 23:30:00 339
2019-01-11 23:45:00 325
2019-01-12 00:00:00 310
2019-01-12 00:15:00 284
2019-01-12 00:30:00 181
2019-01-12 00:45:00 162
2019-01-12 01:00:00 143
2019-01-12 01:15:00 122
2019-01-12 23:00:00 120
2019-01-12 23:15:00 122
2019-01-12 23:30:00 124
2019-01-12 23:45:00 107
2019-01-13 00:00:00 118
2019-01-13 00:15:00 123
2019-01-13 00:30:00 127
2019-01-13 00:45:00 124
2019-01-13 01:00:00 126
2019-01-13 01:15:00 139
2019-01-13 20:00:00 145
2019-01-13 20:15:00 141
2019-01-13 20:30:00 135
2019-01-13 20:45:00 128
2019-01-13 21:00:00 122
2019-01-13 21:15:00 119
2019-01-13 21:30:00 111
2019-01-13 21:45:00 95
2019-01-13 22:00:00 94
2019-01-13 22:15:00 106
2019-01-13 22:30:00 138
2019-01-13 22:45:00 164
2019-01-13 23:00:00 190
2019-01-13 23:15:00 171
2019-01-13 23:30:00 179
2019-01-15 20:00:00 102
2019-01-15 20:15:00 104
2019-01-15 20:30:00 109
2019-01-15 20:45:00 115
2019-01-15 21:00:00 117
2019-01-15 21:15:00 114
2019-01-15 21:30:00 107
2019-01-15 21:45:00 101
2019-01-15 22:00:00 101
2019-01-15 22:15:00 80
2019-01-15 22:30:00 96
2019-01-15 22:45:00 115
2019-01-15 23:00:00 120
2019-01-16 20:00:00 164
2019-01-16 20:15:00 160
2019-01-16 20:30:00 146
2019-01-16 20:45:00 133
2019-01-16 21:00:00 129
2019-01-16 21:15:00 124
2019-01-16 21:30:00 119
2019-01-16 21:45:00 116
2019-01-16 22:00:00 117
2019-01-16 22:15:00 122
2019-01-16 22:30:00 120
2019-01-16 22:45:00 127
2019-01-16 23:00:00 137
2019-01-16 23:15:00 133
2019-01-16 23:30:00 127
2019-01-16 23:45:00 126
2019-01-17 00:00:00 130
2019-01-17 00:15:00 134
2019-01-17 00:45:00 142
2019-01-17 01:00:00 144
2019-01-17 01:30:00 146
2019-01-17 01:45:00 153
2019-01-17 20:15:00 145
2019-01-17 20:30:00 140
2019-01-17 20:45:00 140
2019-01-17 21:00:00 153
2019-01-17 21:15:00 148
2019-01-17 21:30:00 164
2019-01-17 21:45:00 195
2019-01-17 22:00:00 192
2019-01-17 22:15:00 178
2019-01-17 22:30:00 167
2019-01-19 01:30:00 147
2019-01-19 01:45:00 140
2019-01-19 02:00:00 133
2019-01-19 02:15:00 131
2019-01-19 02:30:00 129
2019-01-19 02:45:00 127
2019-01-19 03:00:00 126
2019-01-19 03:15:00 121
2019-01-19 03:30:00 117
2019-01-19 03:45:00 116
2019-01-19 04:00:00 140
2019-01-19 04:15:00 138
2019-01-19 04:30:00 132
2019-01-19 04:45:00 124
2019-01-19 05:00:00 120
2019-01-19 05:15:00 115
2019-01-19 05:30:00 114
2019-01-19 05:45:00 112
2019-01-19 22:00:00 177
2019-01-20 05:45:00 178
2019-01-20 20:15:00 218
2019-01-20 20:30:00 212
2019-01-20 20:45:00 206
2019-01-20 21:00:00 204
2019-01-20 21:15:00 199
2019-01-20 21:30:00 227
2019-01-20 21:45:00 165
2019-01-20 22:00:00 156
2019-01-20 22:15:00 152
2019-01-20 22:30:00 148
2019-01-20 22:45:00 141
2019-01-20 23:00:00 140
2019-01-20 23:15:00 158
2019-01-20 23:30:00 161
2019-01-20 23:45:00 159
2019-01-21 20:00:00 166
2019-01-21 20:15:00 158
2019-01-21 20:30:00 159
2019-01-21 20:45:00 167
2019-01-21 21:00:00 175
2019-01-21 21:15:00 184
2019-01-21 21:30:00 196
2019-01-21 21:45:00 200
2019-01-21 22:00:00 175
2019-01-21 22:15:00 172
2019-01-21 22:30:00 158
2019-01-21 22:45:00 142
2019-01-21 23:00:00 126
2019-01-22 22:15:00 198
2019-01-22 22:30:00 195
2019-01-22 22:45:00 196
2019-01-22 23:00:00 209
2019-01-22 23:15:00 156
2019-01-22 23:30:00 157
2019-01-22 23:45:00 152
2019-01-23 00:00:00 144
2019-01-23 00:15:00 135
2019-01-23 00:30:00 131
2019-01-23 00:45:00 134
2019-01-23 01:00:00 153
2019-01-23 01:15:00 156
2019-01-23 01:30:00 155
2019-01-23 01:45:00 154
2019-01-23 02:00:00 159
2019-01-23 02:15:00 165
2019-01-23 02:30:00 168
2019-01-23 02:45:00 165
2019-01-23 03:00:00 159
2019-01-23 03:15:00 153
2019-01-23 03:30:00 147
2019-01-23 03:45:00 140
2019-01-23 04:00:00 134
2019-01-23 04:15:00 130
2019-01-23 04:30:00 128
2019-01-23 04:45:00 127
2019-01-23 05:00:00 124
2019-01-23 05:15:00 118
2019-01-23 05:30:00 114
2019-01-23 05:45:00 113
2019-01-23 21:45:00 222
2019-01-23 22:15:00 198
2019-01-24 21:45:00 111
2019-01-24 22:00:00 115
2019-01-24 22:15:00 103
2019-01-25 20:00:00 152
2019-01-25 20:15:00 155
2019-01-25 20:30:00 168
2019-01-25 20:45:00 175
2019-01-25 21:00:00 174
2019-01-25 21:15:00 173
2019-01-25 21:30:00 164
2019-01-25 21:45:00 198
2019-01-25 22:00:00 216
2019-01-25 22:15:00 280
2019-01-25 22:30:00 279
2019-01-25 22:45:00 277
2019-01-25 23:00:00 279
2019-01-25 23:15:00 279
2019-01-25 23:30:00 276
2019-01-25 23:45:00 272
2019-01-26 00:00:00 271
2019-01-26 00:15:00 215
2019-01-26 00:30:00 212
2019-01-26 00:45:00 202
2019-01-26 01:00:00 195
2019-01-26 22:15:00 192
2019-01-26 22:30:00 204
2019-01-26 22:45:00 255
2019-01-26 23:00:00 241
2019-01-26 23:15:00 215
2019-01-26 23:30:00 191
2019-01-26 23:45:00 176
2019-01-27 00:00:00 156
2019-01-27 00:15:00 154
2019-01-27 00:30:00 147
2019-01-27 00:45:00 138
2019-01-27 01:00:00 134
2019-01-27 01:15:00 190
2019-01-27 01:30:00 185
2019-01-27 01:45:00 184
2019-01-27 02:00:00 175
2019-01-27 02:15:00 169
2019-01-27 02:30:00 168
2019-01-27 02:45:00 170
2019-01-27 03:00:00 164
2019-01-27 03:15:00 155
2019-01-27 03:30:00 152
2019-01-27 03:45:00 151
2019-01-27 04:00:00 148
2019-01-27 04:15:00 145
2019-01-27 04:30:00 148
2019-01-27 04:45:00 152
2019-01-27 05:00:00 154
2019-01-27 05:15:00 156
2019-01-27 05:30:00 157
2019-01-27 05:45:00 157
2019-01-27 22:45:00 251
2019-01-27 23:00:00 219
2019-01-27 23:15:00 207
2019-01-27 23:30:00 194
2019-01-27 23:45:00 176
2019-01-28 00:00:00 162
2019-01-28 00:15:00 150
2019-01-28 02:00:00 103
2019-01-28 02:15:00 101
2019-01-28 02:30:00 98
2019-01-28 02:45:00 96
2019-01-28 03:00:00 93
2019-01-28 03:15:00 88
2019-01-28 03:30:00 84
2019-01-28 03:45:00 82
2019-01-28 04:00:00 84
2019-01-28 04:15:00 86
2019-01-28 04:30:00 85
2019-01-28 04:45:00 83
2019-01-28 05:00:00 84
2019-01-28 05:15:00 83
2019-01-28 05:30:00 82
2019-01-28 05:45:00 80
2019-01-28 20:00:00 166
2019-01-28 20:15:00 166
2019-01-28 20:30:00 167
2019-01-28 20:45:00 184
2019-01-28 21:00:00 193
2019-01-28 21:15:00 193
2019-01-28 21:30:00 194
2019-01-28 21:45:00 226
2019-01-28 22:00:00 196
2019-01-28 22:15:00 193
2019-01-28 22:30:00 184
2019-01-28 22:45:00 176
2019-01-28 23:00:00 180
2019-01-28 23:15:00 208
2019-01-28 23:30:00 215
2019-01-28 23:45:00 188
2019-01-29 00:00:00 187
2019-01-29 00:15:00 189
2019-01-29 00:30:00 191
2019-01-29 00:45:00 190
2019-01-29 01:00:00 187
2019-01-29 01:15:00 184
2019-01-29 01:30:00 181
2019-01-29 01:45:00 169
2019-01-29 02:00:00 165
2019-01-29 02:15:00 162
2019-01-29 02:30:00 169
2019-01-29 02:45:00 178
2019-01-29 03:00:00 175
2019-01-29 03:15:00 170
2019-01-29 03:30:00 168
2019-01-29 03:45:00 166
2019-01-29 04:00:00 163
2019-01-29 04:15:00 159
2019-01-29 04:30:00 156
2019-01-29 04:45:00 156
2019-01-29 05:00:00 155
2019-01-29 05:30:00 143
2019-01-30 21:30:00 139
2019-01-30 21:45:00 140
2019-01-30 22:00:00 144
2019-01-30 22:15:00 138
2019-01-30 22:30:00 137
2019-01-30 22:45:00 155
2019-01-30 23:00:00 159
2019-01-30 23:15:00 178
2019-01-30 23:30:00 177
2019-01-30 23:45:00 174
2019-01-31 00:00:00 178
2019-01-31 00:15:00 185
2019-01-31 00:30:00 181
2019-01-31 00:45:00 178
2019-01-31 01:00:00 181
2019-01-31 01:15:00 184
2019-01-31 01:30:00 186
2019-01-31 01:45:00 185
2019-01-31 02:00:00 178
2019-01-31 02:15:00 166
2019-01-31 02:30:00 166
2019-01-31 02:45:00 160
2019-01-31 03:00:00 150
2019-01-31 03:15:00 161
2019-01-31 03:30:00 162
2019-01-31 03:45:00 156
2019-01-31 04:00:00 150
2019-01-31 04:15:00 146
2019-01-31 04:30:00 142
2019-01-31 04:45:00 136
2019-01-31 05:00:00 131
2019-01-31 05:15:00 128
2019-01-31 05:30:00 126
2019-01-31 05:45:00 124
2019-01-31 21:00:00 165
2019-01-31 21:15:00 168
2019-01-31 21:30:00 168
2019-01-31 21:45:00 172
2019-01-31 22:00:00 176
2019-01-31 22:15:00 167
2019-01-31 22:30:00 154
2019-01-31 22:45:00 146
2019-01-31 23:00:00 145
2019-01-31 23:15:00 144
2019-01-31 23:30:00 141
2019-01-31 23:45:00 145

Las horas de cada día del mes en las que ha estado en tiempo en rango, junto a su porcentaje de tiempo en rango del paciente 22. (1/2019)¶

In [51]:
import pandas as pd

# Configurar la opción display.max_columns y display.max_colwidth para mostrar todas las columnas y el texto completo en las celdas
pd.set_option('display.max_columns', None)
pd.set_option('display.max_colwidth', None)

# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df[(df['Patient_ID'] == 22) & (df['Measurement_date'].dt.year == 2019) & (df['Measurement_date'].dt.month == 1)]

# Filtrar solo las mediciones que están en rango (entre 70 y 180)
in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement'] >= 70) & (df_paciente_22_1_2019['Measurement'] <= 180)]

# Agrupar los datos por día y extraer las horas en las que se estuvo en rango
daily_hours_in_range = in_range.groupby(in_range['Measurement_date'].dt.date)['Measurement_time'].apply(lambda x: x.dt.hour.unique())

# Agrupar los datos por día
daily_data = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.date)

# Calcular el porcentaje de tiempo en rango para cada día
daily_percentage_in_range = daily_data.apply(lambda x: (x['Measurement'].between(70, 180)).mean() * 100)

# Crear un nuevo DataFrame con los valores de las horas en rango y el porcentaje de tiempo en rango para cada día
df_resultados = pd.DataFrame({'Día': daily_data.groups.keys(),
                              'Horas en rango': [f'[{", ".join(map(str, daily_hours_in_range.get(day, [])))}]' for day in daily_data.groups.keys()],
                              '% tiempo en rango': [f'{daily_percentage_in_range.get(day, 0):.2f}' for day in daily_data.groups.keys()]})

# Mostrar el resultado con los valores de la columna '% tiempo en rango' centrados
df_resultados.style.set_properties(subset=['% tiempo en rango'], **{'text-align': 'center'})
Out[51]:
  Día Horas en rango % tiempo en rango
0 2019-01-01 [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 73.33
1 2019-01-02 [0, 1, 2, 3, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 74.67
2 2019-01-03 [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 100.00
3 2019-01-04 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20] 66.67
4 2019-01-05 [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 72.92
5 2019-01-06 [2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 21, 22, 23] 53.95
6 2019-01-07 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23] 90.24
7 2019-01-08 [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23] 87.80
8 2019-01-09 [0, 1, 2, 3, 4, 5, 6, 11, 14, 16, 17, 18, 21, 22, 23] 45.00
9 2019-01-10 [0, 2, 3, 11, 12, 13, 14, 23] 91.67
10 2019-01-11 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 18, 20, 21] 83.87
11 2019-01-12 [0, 1, 9, 10, 11, 12, 13, 14, 15, 17, 18, 23] 72.00
12 2019-01-13 [0, 1, 8, 9, 10, 11, 12, 13, 14, 18, 19, 20, 21, 22, 23] 67.21
13 2019-01-15 [6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23] 100.00
14 2019-01-16 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 96.00
15 2019-01-17 [0, 1, 20, 21, 22] 87.50
16 2019-01-19 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22] 100.00
17 2019-01-20 [5, 6, 8, 9, 10, 11, 12, 13, 14, 16, 17, 21, 22, 23] 63.64
18 2019-01-21 [19, 20, 21, 22, 23] 47.83
19 2019-01-22 [23] 42.86
20 2019-01-23 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17] 88.73
21 2019-01-24 [21, 22] 100.00
22 2019-01-25 [11, 12, 15, 16, 17, 18, 19, 20, 21] 55.77
23 2019-01-26 [23] 8.33
24 2019-01-27 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 23] 75.38
25 2019-01-28 [0, 2, 3, 4, 5, 6, 7, 14, 15, 16, 17, 18, 19, 20, 22, 23] 81.97
26 2019-01-29 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 80.56
27 2019-01-30 [21, 22, 23] 100.00
28 2019-01-31 [0, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 19, 21, 22, 23] 83.33

Extrae el porcentaje de tiempo en rango de la mañana, tarde y noche del paciente 22 (1/2019)¶

In [52]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Calcular el porcentaje de tiempo en rango para la mañana (entre las 6:00 am y las 12:00 pm)
morning_in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 6) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 12)]
morning_percentage_in_range = (morning_in_range['Measurement'].between(70, 180)).mean() * 100

# Calcular el porcentaje de tiempo en rango para la tarde (entre las 12:00 pm y las 8:00 pm)
afternoon_in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 12) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 20)]
afternoon_percentage_in_range = (afternoon_in_range['Measurement'].between(70, 180)).mean() * 100

# Calcular el porcentaje de tiempo en rango para la noche (entre las 8:00 pm y las 6:00 am del día siguiente)
night_in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 20) | (df_paciente_22_1_2019['Measurement_time'].dt.hour < 6)]
night_percentage_in_range = (night_in_range['Measurement'].between(70, 180)).mean() * 100

# Crear un nuevo DataFrame con los resultados
df_resultados = pd.DataFrame({'Período del día': ['Mañana', 'Tarde', 'Noche'],
                              '% tiempo en rango': [f'{morning_percentage_in_range:.2f}%',
                                                    f'{afternoon_percentage_in_range:.2f}%',
                                                    f'{night_percentage_in_range:.2f}%']})
# Mostrar el resultado
display(df_resultados)
Período del día % tiempo en rango
0 Mañana 88.17%
1 Tarde 71.01%
2 Noche 74.86%

Gráfico de barras del tiempo en rango de la mañana, tarde y noche del paciente 83 (10/2021)¶

In [53]:
import matplotlib.pyplot as plt

# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df[(df['Patient_ID'] == 22) & (df['Measurement_date'].dt.year == 2019) & (df['Measurement_date'].dt.month == 1)]

# Calcular el porcentaje de tiempo en rango para la mañana (entre las 6:00 am y las 12:00 pm)
morning_in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 6) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 12)]
morning_percentage_in_range = (morning_in_range['Measurement'].between(70, 180)).mean() * 100

# Calcular el porcentaje de tiempo en rango para la tarde (entre las 12:00 pm y las 8:00 pm)
afternoon_in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 12) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 20)]
afternoon_percentage_in_range = (afternoon_in_range['Measurement'].between(70, 180)).mean() * 100

# Calcular el porcentaje de tiempo en rango para la noche (entre las 8:00 pm y las 6:00 am del día siguiente)
night_in_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 20) | (df_paciente_22_1_2019['Measurement_time'].dt.hour < 6)]
night_percentage_in_range = (night_in_range['Measurement'].between(70, 180)).mean() * 100

# Crear un nuevo DataFrame con los porcentajes de tiempo en rango para la mañana, tarde y noche como columnas
result = pd.DataFrame({'Mañana': [morning_percentage_in_range], 'Tarde': [afternoon_percentage_in_range], 'Noche': [night_percentage_in_range]})

# Crear un gráfico de barras mostrando los porcentajes de tiempo en rango para la mañana, tarde y noche
ax = result.plot(kind='bar', rot=0, color=['#0e4d82', '#bb3f11', '#7d6c5c'])

# Establecer las etiquetas de los ejes X e Y
ax.set_xlabel('Hora del día')
ax.set_ylabel('% Tiempo en rango')

# Añadir título al gráfico
plt.title('Porcentaje de tiempo en rango por hora del día')

# Establecer el rango del eje Y
plt.ylim([0, 100])

# Mostrar los valores exactos de los porcentajes dentro de las barras
for p in ax.patches:
    ax.annotate(f'{p.get_height():.2f}%', (p.get_x() + p.get_width() / 2., p.get_height() / 2), ha='center', va='center', color='white', fontweight='bold')

plt.show()

Extraer la media de la glucosa para cada hora de todos los día del mes del paciente 22 (1/2019)¶

In [54]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df[(df['Patient_ID'] == 22) & (df['Measurement_date'].dt.year == 2019) & (df['Measurement_date'].dt.month == 1)]

# Agrupar los datos por hora
hourly_data = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_time'].dt.hour)

# Calcular la media de la columna Measurement para cada hora
hourly_mean = hourly_data['Measurement'].mean()

# Crear un nuevo DataFrame con los resultados
df_resultados = pd.DataFrame({'Hora': hourly_mean.index, 'Media': hourly_mean.values})

# Formatear la columna Media para mostrar solo dos decimales
df_resultados['Media'] = df_resultados['Media'].apply(lambda x: '{:.2f}'.format(x))

# Mostrar el resultado en forma de tabla de DataFrame sin índice
df_resultados.style.hide(axis='index')
Out[54]:
Hora Media
0 165.51
1 160.29
2 156.67
3 154.38
4 148.72
5 143.32
6 139.92
7 144.65
8 151.18
9 147.76
10 151.13
11 156.65
12 167.71
13 159.31
14 148.80
15 169.62
16 169.23
17 158.60
18 162.03
19 170.74
20 165.62
21 161.85
22 165.24
23 169.26

Extraer la hora (3 mayores porcentajes) con el mayor porcentaje en tiempo en rango y su porcentaje de tiempo en rango del paciente 22. (1/2019)¶

También extrae el porcentaje de tiempo en rango de cada hora

In [55]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Agrupar los datos por hora
hourly_data = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_time'].dt.hour)

# Calcular el porcentaje de tiempo en rango para cada hora
hourly_percentage_in_range = hourly_data.apply(lambda x: (x['Measurement'].between(70, 180)).mean() * 100)

# Encontrar la hora o las horas con el mayor porcentaje de tiempo en rango
max_percentage = hourly_percentage_in_range.max()
best_hours = hourly_percentage_in_range[hourly_percentage_in_range == max_percentage].index
best_hours_str = ', '.join(map(str, best_hours.tolist()))

# Crear un DataFrame con la hora o las horas con el mayor porcentaje de tiempo en rango
df_best_hours = pd.DataFrame({'Hora': [best_hours_str], 'Porcentaje de tiempo en rango': [f'{max_percentage:.2f}%']})

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('La hora con el mayor porcentaje de tiempo en rango es:')
display(df_best_hours.style.hide(axis='index'))

top_3 = hourly_percentage_in_range.nlargest(3)

# Crear un DataFrame con las 3 mayores porcentajes de tiempo en rango
df_top_3 = pd.DataFrame({'Hora': top_3.index, 'Porcentaje de tiempo en rango': top_3.apply(lambda x: f'{x:.2f}%')})

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('\nLas 3 mayores porcentajes de tiempo en rango son:')
display(df_top_3.style.hide(axis='index'))

# Crear un DataFrame con todos los porcentajes de tiempo en rango
df_all_percentages = pd.DataFrame({'Hora': hourly_percentage_in_range.index, 'Porcentaje de tiempo en rango': hourly_percentage_in_range.apply(lambda x: f'{x:.2f}%')})

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('\nTodos los porcentajes de tiempo en rango son:')
display(df_all_percentages.style.hide(axis='index'))
La hora con el mayor porcentaje de tiempo en rango es:
Hora Porcentaje de tiempo en rango
4 93.10%
Las 3 mayores porcentajes de tiempo en rango son:
Hora Porcentaje de tiempo en rango
4 93.10%
5 92.98%
9 91.38%
Todos los porcentajes de tiempo en rango son:
Hora Porcentaje de tiempo en rango
0 62.69%
1 61.29%
2 87.30%
3 80.88%
4 93.10%
5 92.98%
6 88.71%
7 88.71%
8 89.09%
9 91.38%
10 88.52%
11 82.46%
12 66.67%
13 80.00%
14 84.06%
15 70.69%
16 66.67%
17 65.08%
18 68.12%
19 64.52%
20 75.00%
21 66.67%
22 69.32%
23 68.97%

Las horas de cada día del mes en las que ha estado en Hipoglucemia (<70) o Hiperglucemia (>180), junto a su porcentaje de Hipoglucemia (<70) o Hiperglucemia (>180) del paciente 22. (1/2019)¶

In [56]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Filtrar solo las mediciones que están fuera de rango (menor a 70 o mayor a 180)
out_of_range = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement'] < 70) | (df_paciente_22_1_2019['Measurement'] > 180)]

# Agrupar los datos por día y extraer las horas en las que se estuvo fuera de rango
daily_hours_out_of_range = out_of_range.groupby(out_of_range['Measurement_date'].dt.date)['Measurement_time'].apply(lambda x: x.dt.hour.unique())

# Agrupar los datos por día
daily_data = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_date'].dt.date)

# Calcular el porcentaje de tiempo en hipoglucemia (<70) y hiperglucemia (>180) para cada día
daily_percentage_hypo = daily_data.apply(lambda x: (x['Measurement'] < 70).mean() * 100)
daily_percentage_hyper = daily_data.apply(lambda x: (x['Measurement'] > 180).mean() * 100)

# Crear un DataFrame con los resultados
data = []
for day in daily_data.groups.keys():
    hours = daily_hours_out_of_range.get(day, [])
    hours_str = f'[{", ".join(map(str, hours))}]'
    percentage_hypo = daily_percentage_hypo.get(day, 0)
    percentage_hyper = daily_percentage_hyper.get(day, 0)
    data.append({'Día': str(day), 'Horas fuera de rango': hours_str, '% tiempo en hipoglucemia': f'{percentage_hypo:.2f}%', '% tiempo en hiperglucemia': f'{percentage_hyper:.2f}%'})
df_resultados = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
display(df_resultados.style.hide(axis='index'))
Día Horas fuera de rango % tiempo en hipoglucemia % tiempo en hiperglucemia
2019-01-01 [0, 1, 2, 3] 0.00% 26.67%
2019-01-02 [3, 4, 5, 6, 17, 18] 0.00% 25.33%
2019-01-03 [] 0.00% 0.00%
2019-01-04 [2, 3, 16, 17, 18, 19, 20, 21, 22, 23] 0.00% 33.33%
2019-01-05 [0, 1, 19, 20, 21, 22, 23] 0.00% 27.08%
2019-01-06 [0, 1, 2, 15, 16, 17, 18, 19, 20, 21] 0.00% 46.05%
2019-01-07 [10, 11, 18, 19] 0.00% 9.76%
2019-01-08 [3, 12, 15, 16] 0.00% 12.20%
2019-01-09 [1, 2, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 21] 0.00% 55.00%
2019-01-10 [12, 14] 0.00% 8.33%
2019-01-11 [11, 21, 22, 23] 0.00% 16.13%
2019-01-12 [0, 12, 16, 17, 19] 0.00% 28.00%
2019-01-13 [10, 14, 15, 16, 17, 18, 23] 0.00% 32.79%
2019-01-15 [] 0.00% 0.00%
2019-01-16 [13] 0.00% 4.00%
2019-01-17 [21, 22] 0.00% 12.50%
2019-01-19 [] 0.00% 0.00%
2019-01-20 [6, 7, 11, 17, 18, 19, 20, 21] 0.00% 36.36%
2019-01-21 [17, 18, 19, 21] 0.00% 52.17%
2019-01-22 [22, 23] 0.00% 57.14%
2019-01-23 [11, 12, 13, 21, 22] 0.00% 11.27%
2019-01-24 [] 0.00% 0.00%
2019-01-25 [12, 13, 14, 15, 21, 22, 23] 0.00% 44.23%
2019-01-26 [0, 1, 22, 23] 0.00% 91.67%
2019-01-27 [1, 11, 12, 13, 22, 23] 0.00% 24.62%
2019-01-28 [20, 21, 22, 23] 0.00% 18.03%
2019-01-29 [0, 1] 0.00% 19.44%
2019-01-30 [] 0.00% 0.00%
2019-01-31 [0, 1, 8, 9, 16] 0.00% 16.67%

Extrae el porcentaje de tiempo en en Hipoglucemia (<70) y Hiperglucemia (>180) de la mañana, tarde y noche del paciente 22. (1/2019)¶

In [57]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Calcular el porcentaje de tiempo en hipoglucemia (<70) y hiperglucemia (>180) para la mañana (entre las 6:00 am y las 12:00 pm)
morning_data = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 6) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 12)]
morning_percentage_hypo = (morning_data['Measurement'] < 70).mean() * 100
morning_percentage_hyper = (morning_data['Measurement'] > 180).mean() * 100

# Calcular el porcentaje de tiempo en hipoglucemia (<70) y hiperglucemia (>180) para la tarde (entre las 12:00 pm y las 8:00 pm)
afternoon_data = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 12) & (df_paciente_22_1_2019['Measurement_time'].dt.hour < 20)]
afternoon_percentage_hypo = (afternoon_data['Measurement'] < 70).mean() * 100
afternoon_percentage_hyper = (afternoon_data['Measurement'] > 180).mean() * 100

# Calcular el porcentaje de tiempo en hipoglucemia (<70) y hiperglucemia (>180) para la noche (entre las 8:00 pm y las 6:00 am del día siguiente)
night_data = df_paciente_22_1_2019[(df_paciente_22_1_2019['Measurement_time'].dt.hour >= 20) | (df_paciente_22_1_2019['Measurement_time'].dt.hour < 6)]
night_percentage_hypo = (night_data['Measurement'] < 70).mean() * 100
night_percentage_hyper = (night_data['Measurement'] > 180).mean() * 100

# Crear un DataFrame con los resultados
data = [{'Parte del día': 'Mañana', '% tiempo en hipoglucemia': f'{morning_percentage_hypo:.2f}%', '% tiempo en hiperglucemia': f'{morning_percentage_hyper:.2f}%'},
        {'Parte del día': 'Tarde', '% tiempo en hipoglucemia': f'{afternoon_percentage_hypo:.2f}%', '% tiempo en hiperglucemia': f'{afternoon_percentage_hyper:.2f}%'},
        {'Parte del día': 'Noche', '% tiempo en hipoglucemia': f'{night_percentage_hypo:.2f}%', '% tiempo en hiperglucemia': f'{night_percentage_hyper:.2f}%'}]
df_resultados = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
display(df_resultados.style.hide(axis='index'))
Parte del día % tiempo en hipoglucemia % tiempo en hiperglucemia
Mañana 0.00% 11.83%
Tarde 0.00% 28.99%
Noche 0.00% 25.14%

Extraer la hora (3 mayores porcentajes) con el mayor porcentaje en Hipoglucemia (<70) y Hiperglucemia (>180) y su porcentaje de Hipoglucemia (<70) o Hiperglucemia (>180) del paciente 22. (1/2019)¶

También extrae el porcentaje de tiempo en rango de cada hora

In [58]:
# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_paciente_22_1_2019 = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Agrupar los datos por hora
hourly_data = df_paciente_22_1_2019.groupby(df_paciente_22_1_2019['Measurement_time'].dt.hour)

# Calcular el porcentaje de tiempo en hipoglucemia (<70) e hiperglucemia (>180) para cada hora
hourly_percentage_hypo = hourly_data.apply(lambda x: (x['Measurement'] < 70).mean() * 100)
hourly_percentage_hyper = hourly_data.apply(lambda x: (x['Measurement'] > 180).mean() * 100)

# Encontrar la hora o las horas con el mayor porcentaje de tiempo en hipoglucemia
max_percentage_hypo = hourly_percentage_hypo.max()
best_hours_hypo = hourly_percentage_hypo[hourly_percentage_hypo == max_percentage_hypo].index
best_hours_str_hypo = ', '.join(map(str, best_hours_hypo.tolist()))

# Encontrar la hora o las horas con el mayor porcentaje de tiempo en hiperglucemia
max_percentage_hyper = hourly_percentage_hyper.max()
best_hours_hyper = hourly_percentage_hyper[hourly_percentage_hyper == max_percentage_hyper].index
best_hours_str_hyper = ', '.join(map(str, best_hours_hyper.tolist()))

# Crear un DataFrame con la hora o las horas con el mayor porcentaje de tiempo en hipoglucemia e hiperglucemia
data = [{'Hora': best_hours_str_hypo, 'Porcentaje de tiempo': f'{max_percentage_hypo:.2f}%', 'Tipo': 'Hipoglucemia'},
        {'Hora': best_hours_str_hyper, 'Porcentaje de tiempo': f'{max_percentage_hyper:.2f}%', 'Tipo': 'Hiperglucemia'}]
df_best_hours = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('La hora con el mayor porcentaje de tiempo en hipoglucemia e hiperglucemia es:')
display(df_best_hours.style.hide(axis='index'))

# Encontrar las 3 horas con los mayores porcentajes de tiempo en hipoglucemia
top_3_hypo = hourly_percentage_hypo.nlargest(3)

# Crear un DataFrame con las 3 horas con los mayores porcentajes de tiempo en hipoglucemia
data = [{'Hora': hour, 'Porcentaje de tiempo': f'{percentage:.2f}%'} for hour, percentage in top_3_hypo.items()]
df_top_3_hypo = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('\nLas 3 mayores porcentajes de tiempo en hipoglucemia son:')
display(df_top_3_hypo.style.hide(axis='index'))

# Encontrar las 3 horas con los mayores porcentajes de tiempo en hiperglucemia
top_3_hyper = hourly_percentage_hyper.nlargest(3)

# Crear un DataFrame con las 3 horas con los mayores porcentajes de tiempo en hiperglucemia
data = [{'Hora': hour, 'Porcentaje de tiempo': f'{percentage:.2f}%'} for hour, percentage in top_3_hyper.items()]
df_top_3_hyper = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('\nLas 3 mayores porcentajes de tiempo en hiperglucemia son:')
display(df_top_3_hyper.style.hide(axis='index'))

# Crear un DataFrame con todos los porcentajes de tiempo en hipoglucemia e hiperglucemia para cada hora
data = [{'Hora': hour, '% tiempo en hipoglucemia': f'{hourly_percentage_hypo.get(hour, 0):.2f}%', '% tiempo en hiperglucemia': f'{hourly_percentage_hyper.get(hour, 0):.2f}%'} for hour in range(24)]
df_all_percentages = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('\nTodos los porcentajes de tiempo en hipoglucemia e hiperglucemia para cada hora son:')
display(df_all_percentages.style.hide(axis='index'))
La hora con el mayor porcentaje de tiempo en hipoglucemia e hiperglucemia es:
Hora Porcentaje de tiempo Tipo
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 0.00% Hipoglucemia
1 38.71% Hiperglucemia
Las 3 mayores porcentajes de tiempo en hipoglucemia son:
Hora Porcentaje de tiempo
0 0.00%
1 0.00%
2 0.00%
Las 3 mayores porcentajes de tiempo en hiperglucemia son:
Hora Porcentaje de tiempo
1 38.71%
0 37.31%
19 35.48%
Todos los porcentajes de tiempo en hipoglucemia e hiperglucemia para cada hora son:
Hora % tiempo en hipoglucemia % tiempo en hiperglucemia
0 0.00% 37.31%
1 0.00% 38.71%
2 0.00% 12.70%
3 0.00% 19.12%
4 0.00% 6.90%
5 0.00% 7.02%
6 0.00% 11.29%
7 0.00% 11.29%
8 0.00% 10.91%
9 0.00% 8.62%
10 0.00% 11.48%
11 0.00% 17.54%
12 0.00% 33.33%
13 0.00% 20.00%
14 0.00% 15.94%
15 0.00% 29.31%
16 0.00% 33.33%
17 0.00% 34.92%
18 0.00% 31.88%
19 0.00% 35.48%
20 0.00% 25.00%
21 0.00% 33.33%
22 0.00% 30.68%
23 0.00% 31.03%

Muestra si hay mediciones de datos en todos los meses del año del paciente 22¶

In [59]:
df_paciente_22_2019 = df_paciente_22_2019[(df_paciente_22_2019['Patient_ID'] == 22) & (df_paciente_22_2019['Measurement_date'].dt.year == 2019)]
meses = df_paciente_22_2019['Measurement_date'].dt.month.unique()

# Crear un DataFrame con los meses para los que hay datos
df_meses = pd.DataFrame({'Meses': meses})

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('Hay datos para los siguientes meses del año 2019 para el paciente 22:')
display(df_meses.style.hide(axis='index'))

if len(meses) == 12:
    print('Hay datos para todos los meses del año 2019 para el paciente 22')
else:
    print('Faltan datos para algunos meses del año 2019 para el paciente 22')
Hay datos para los siguientes meses del año 2019 para el paciente 22:
Meses
1
2
3
4
5
6
8
9
10
11
12
Faltan datos para algunos meses del año 2019 para el paciente 22
In [60]:
# Crear una lista de todos los meses en un año
todos_meses = list(range(1,13))

df_paciente_22_2019 = df_paciente_22_2019[(df_paciente_22_2019['Patient_ID'] == 22) & (df_paciente_22_2019['Measurement_date'].dt.year == 2019)]
meses_con_datos = df_paciente_22_2019['Measurement_date'].dt.month.unique()

# Encontrar los meses que no están en la lista de meses con datos
meses_sin_datos = [mes for mes in todos_meses if mes not in meses_con_datos]

# Crear un DataFrame con los meses para los que hay datos y los que no
df_meses_con_datos = pd.DataFrame({'Meses con datos': meses_con_datos})
df_meses_sin_datos = pd.DataFrame({'Meses sin datos': meses_sin_datos})

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('Para el paciente 22 en el año 2019:')
display(df_meses_con_datos.style.hide(axis='index'))
display(df_meses_sin_datos.style.hide(axis='index'))

if len(meses_con_datos) == 12:
    print('Hay datos para todos los meses.')
else:
    print('Faltan datos para algunos meses.')
Para el paciente 22 en el año 2019:
Meses con datos
1
2
3
4
5
6
8
9
10
11
12
Meses sin datos
7
Faltan datos para algunos meses.

Mostrar la media de la hemoglobina glicosilada HbA1c de todos los meses del año 2019 del paciente 22¶

In [61]:
df_filtered = df_paciente_22_2019[(df_paciente_22_2019['Patient_ID'] == 22) & (df_paciente_22_2019['Measurement_date'].dt.year == 2019)]

# Crear un DataFrame con la media de la hemoglobina glicosilada HbA1c para cada mes
data = []
for month in df_filtered['Measurement_date'].dt.month.unique():
    df_month = df_filtered[df_filtered['Measurement_date'].dt.month == month]
    eAG = df_month['Measurement'].mean()
    A1c = (eAG + 46.7) / 28.7
    data.append({'Mes': month, 'A1c promedio': f'{A1c:.2f}%'})
df_resultados = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('La media de la hemoglobina glicosilada HbA1c de todos los meses del año 2019 del paciente 22 es:')
display(df_resultados.style.hide(axis='index'))
La media de la hemoglobina glicosilada HbA1c de todos los meses del año 2019 del paciente 22 es:
Mes A1c promedio
1 7.14%
2 7.13%
3 7.27%
4 7.54%
5 7.01%
6 7.29%
8 7.19%
9 7.24%
10 6.89%
11 6.89%
12 7.29%

Gráfico de barras de la hemoglobina glicosilada HbA1c de todos los meses del año 2021 del paciente 83¶

In [62]:
import matplotlib.pyplot as plt

# Filtrar los datos para el paciente 22 en el año 2019
df_filtered = df_paciente_22_2019[(df_paciente_22_2019['Patient_ID'] == 22) & (df_paciente_22_2019['Measurement_date'].dt.year == 2019)]

# Calcular la media de la hemoglobina glicosilada HbA1c para cada mes
months = []
a1c_values = []
for month in df_filtered['Measurement_date'].dt.month.unique():
    df_month = df_filtered[df_filtered['Measurement_date'].dt.month == month]
    eAG = df_month['Measurement'].mean()
    A1c = (eAG + 46.7) / 28.7
    months.append(month)
    a1c_values.append(A1c)

# Crear un nuevo DataFrame con los meses y los valores de HbA1c como columnas
result = pd.DataFrame({'Month': months, 'HbA1c': a1c_values})

# Crear un gráfico de barras o de líneas mostrando la media de la hemoglobina glicosilada HbA1c para cada mes
ax = result.plot.bar(x='Month', y='HbA1c', rot=0)
# ax = result.plot(x='Month', y='HbA1c')

# Establecer las etiquetas de los ejes X e Y
ax.set_xlabel('Month')
ax.set_ylabel('HbA1c (%)')

plt.show()

Mostrar la hemoglobina glicosilada HbA1c de todos los dias del mes 1 del año 2019 del paciente 22¶

In [63]:
df_filtered = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Crear un DataFrame con la hemoglobina glicosilada HbA1c para cada día
data = []
for day in df_filtered['Measurement_date'].dt.day.unique():
    df_day = df_filtered[df_filtered['Measurement_date'].dt.day == day]
    eAG = df_day['Measurement'].mean()
    A1c = (eAG + 46.7) / 28.7
    data.append({'Día': day, 'A1c': f'{A1c:.2f}%'})
df_resultados = pd.DataFrame(data)

# Mostrar el resultado en forma de tabla de DataFrame sin índice
print('La hemoglobina glicosilada HbA1c de todos los días del mes 1, del año 2019, del paciente 22 es:')
display(df_resultados.style.hide(axis='index'))
La hemoglobina glicosilada HbA1c de todos los días del mes 1, del año 2019, del paciente 22 es:
Día A1c
1 7.08%
2 7.00%
3 6.80%
4 7.53%
5 7.34%
6 7.87%
7 6.76%
8 6.96%
9 7.79%
10 7.41%
11 7.52%
12 7.04%
13 7.39%
15 5.96%
16 6.47%
17 7.01%
19 5.97%
20 7.68%
21 7.79%
22 7.91%
23 6.91%
24 5.45%
25 7.73%
26 9.09%
27 7.46%
28 6.29%
29 7.05%
30 7.00%
31 7.03%

Gráfico de barras de la hemoglobina glicosilada HbA1c de todos los dias del mes 1 del año 2019 del paciente 22¶

In [64]:
from matplotlib.figure import Figure
from IPython.display import display

# Filtrar los datos para el paciente 22 en el mes 1 del año 2019
df_filtered = df_paciente_22_1_2019[(df_paciente_22_1_2019['Patient_ID'] == 22) & (df_paciente_22_1_2019['Measurement_date'].dt.year == 2019) & (df_paciente_22_1_2019['Measurement_date'].dt.month == 1)]

# Calcular la hemoglobina glicosilada HbA1c para cada día
days = []
a1c_values = []
for day in df_filtered['Measurement_date'].dt.day.unique():
    df_day = df_filtered[df_filtered['Measurement_date'].dt.day == day]
    eAG = df_day['Measurement'].mean()
    A1c = (eAG + 46.7) / 28.7
    days.append(day)
    a1c_values.append(A1c)

# Crear un nuevo DataFrame con los días y los valores de HbA1c como columnas
result = pd.DataFrame({'Day': days, 'HbA1c': a1c_values})

# Crear una instancia de la clase Figure y establecer su tamaño
fig = Figure()
fig.set_size_inches(5, 5)

# Crear un objeto Axes a partir de la figura
ax = fig.subplots()

# Utilizar el objeto Axes para crear el gráfico de barras
ax.bar(result['Day'], result['HbA1c'])
ax.set_xlabel('Day')
ax.set_ylabel('HbA1c (%)')

# Mostrar la figura
display(fig)

Tabla de hemoglobina glicosilada HbA1c para comprobar la relación que hay entre los niveles de glucosa, la hemoglobina glicosilada y el riesgo de generar complicaciones¶

Tabla de la hemoglobina glicosilada HbA1c

Clarke Error Grid¶

El Clarke Error Grid permite determinar la calidad y precisión del modelo ya que evalúa y cuantifica el riesgo asociado con las estimaciones de glucosa en sangre en comparación con los valores precisos conocidos. Ayuda a determinar qué tan cerca o lejos están las estimaciones de los valores reales y clasificar las estimaciones en diferentes zonas de riesgo. Las zonas de riesgo son A, B, C, D y E:

  • Zona A: Resultados médicamente precisos y cercanos a los valores conocidos. Bajo riesgo y deseable. Valores que están dentro del 20% del valor del sensor de referencia. Estos valores se consideran clínicamente precisos. Por ejemplo, si el valor de referencia es de 100 mg/dL, un valor medido entre 80 y 120 mg/dL estaría en la Zona A. En este caso, el medidor de glucosa proporciona una medición precisa y no se espera que lleve a un tratamiento inapropiado.
  • Zona B: Resultados médicamente aceptables, aunque con una desviación mayor que la zona A. Todavía útiles, pero pueden requerir ajustes. Valores que están fuera del 20% pero que no llevarían a un tratamiento inapropiado. Por ejemplo, si el valor de referencia es de 200 mg/dL, un valor medido de 160 mg/dL estaría en la Zona B. Aunque el valor medido no es completamente preciso, no se espera que lleve a un tratamiento inapropiado.
  • Zona C: Tratamiento innecesario. Estimaciones dentro del rango aceptable, pero alejadas de los valores conocidos. Valores que llevarían a un tratamiento innecesario. Por ejemplo, si el valor de referencia es de 100 mg/dL y el medidor de glucosa indica un nivel alto de glucosa en sangre de 250 mg/dL, el paciente podría recibir tratamiento para reducir su nivel de glucosa en sangre cuando en realidad no lo necesita. Esto podría tener consecuencias negativas para la salud del paciente.
  • Zona D: Incapacidad para detectar una condición peligrosa. Estimaciones fuera del rango aceptable, sin reflejar adecuadamente los niveles reales. Falla potencialmente peligrosa para detectar hipoglucemia o hiperglucemia. Por ejemplo, si el valor de referencia es de 50 mg/dL, lo que indica hipoglucemia, y el medidor de glucosa indica un nivel normal de glucosa en sangre de 100 mg/dL, el paciente podría no recibir tratamiento para aumentar su nivel de glucosa en sangre cuando en realidad lo necesita. Esto podría ser peligroso para la salud del paciente.
  • Zona E: Confusión entre hipoglucemia grave e hiperglucemia. Alto riesgo y crítico. Valores que confundirían el tratamiento de la hipoglucemia con la hiperglucemia y viceversa. Por ejemplo, si el valor de referencia es de 50 mg/dL, lo que indica hipoglucemia, y el medidor de glucosa indica un nivel alto de glucosa en sangre de 250 mg/dL, el paciente podría recibir tratamiento para reducir su nivel de glucosa en sangre cuando en realidad necesita aumentarlo. Esto podría tener consecuencias graves para la salud del paciente.

En resumen, las zonas A y B son las más deseables, indicando resultados precisos o aceptables. La zona C implica un tratamiento innecesario, mientras que la zona D representa la incapacidad para detectar una situación peligrosa. La zona E es la más crítica, indicando la confusión entre hipoglucemia grave e hiperglucemia.

Dataframe Completo¶

In [65]:
df
Out[65]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-02-21 19:45:00 83 2018-02-21 1900-01-01 19:46:00 121 True False False 19 2
2018-02-21 20:00:00 83 2018-02-21 1900-01-01 20:01:00 124 True False False 20 2
2018-02-21 20:15:00 83 2018-02-21 1900-01-01 20:17:00 131 True False False 20 2
2018-02-21 20:30:00 83 2018-02-21 1900-01-01 20:32:00 128 True False False 20 2
2018-02-21 20:45:00 83 2018-02-21 1900-01-01 20:47:00 129 True False False 20 2
... ... ... ... ... ... ... ... ... ...
2022-03-21 19:45:00 27 2022-03-21 1900-01-01 19:48:00 264 False False True 19 0
2022-03-21 20:00:00 27 2022-03-21 1900-01-01 20:03:00 269 False False True 20 0
2022-03-21 20:15:00 27 2022-03-21 1900-01-01 20:18:00 283 False False True 20 0
2022-03-21 20:30:00 27 2022-03-21 1900-01-01 20:33:00 315 False False True 20 0
2022-03-21 20:45:00 27 2022-03-21 1900-01-01 20:48:00 327 False False True 20 0

136340 rows × 9 columns

In [66]:
df.dtypes
Out[66]:
Patient_ID                   Int32
Measurement_date    datetime64[ns]
Measurement_time    datetime64[ns]
Measurement                  Int64
In_Range                      bool
Hypoglycemia                  bool
Hyperglycemia                 bool
Hour_of_Day                  Int64
Day_of_Week                  Int64
dtype: object

DataFrame Completo Paciente 22¶

  • df_paciente_22_2019: Completo del 2019 (6099 rows × 9 columns)
  • df_paciente_22_1_2019: Completo 1/2019 (1565 rows × 9 columns)
In [67]:
df_paciente_22
Out[67]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-11-06 06:45:00 22 2018-11-06 1900-01-01 06:45:00 134 True False False 6 1
2018-11-06 07:00:00 22 2018-11-06 1900-01-01 07:00:00 134 True False False 7 1
2018-11-06 07:15:00 22 2018-11-06 1900-01-01 07:15:00 139 True False False 7 1
2018-11-06 07:30:00 22 2018-11-06 1900-01-01 07:30:00 145 True False False 7 1
2018-11-06 07:45:00 22 2018-11-06 1900-01-01 07:45:00 147 True False False 7 1
... ... ... ... ... ... ... ... ... ...
2022-03-08 11:30:00 22 2022-03-08 1900-01-01 11:30:00 179 True False False 11 1
2022-03-08 12:30:00 22 2022-03-08 1900-01-01 12:30:00 173 True False False 12 1
2022-03-08 12:45:00 22 2022-03-08 1900-01-01 12:45:00 171 True False False 12 1
2022-03-08 13:15:00 22 2022-03-08 1900-01-01 13:15:00 165 True False False 13 1
2022-03-15 23:45:00 22 2022-03-15 1900-01-01 23:46:00 155 True False False 23 1

10409 rows × 9 columns

In [68]:
df_paciente_22.dtypes
Out[68]:
Patient_ID                   Int32
Measurement_date    datetime64[ns]
Measurement_time    datetime64[ns]
Measurement                  Int64
In_Range                      bool
Hypoglycemia                  bool
Hyperglycemia                 bool
Hour_of_Day                  Int64
Day_of_Week                  Int64
dtype: object

Comparativa de soluciones con los modelos RNN (LSTM) y CRNN
¶

Se va a realizar una comparativa de soluciones con los modelos de redes neuronales recurrentes (RNN) y redes neuronales recurrentes convolucionales (CRNN), utilizando series temporales para la predicción de los niveles de glucosa en diabetes tipo 1. El rendimiento de ambos modelos se va a tratar de mejorar mediante la combinación de los distintos parámetros de los algoritmos profundos y de las distintas extracciones de características del conjunto de datos. Para ello se va a utilizar las distintas configuraciones de hiperparámetros LSTM y CRNN.

Las 3 configuraciones de hiperparámetros de LSTM y CRNN son:

Configuración de hiperparámetros LSTM:

  • LSTM_1:
    • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
    • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
    • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
    • units: 32. Número de neuronas en la capa LSTM.
    • activation: 'relu'. Función de activación utilizada en la capa LSTM.
    • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
    • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
    • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
    • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
    • epochs: 20. Número máximo de épocas para entrenar el modelo.
  • LSTM_2:
    • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
    • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
    • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
    • units: 64. Número de neuronas en la capa LSTM.
    • activation: 'relu'. Función de activación utilizada en la capa LSTM.
    • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
    • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
    • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
    • epochs: 20. Número máximo de épocas para entrenar el modelo.
  • LSTM_3:
    • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
    • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
    • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
    • units: 32. Número de neuronas en la capa LSTM.
    • return_sequences: True.
    • activation: 'relu'. Función de activación utilizada en la capa LSTM.
    • kernel_regularizer: L1L2 (0.001 y 0.001).
    • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
    • Dropout: 0.5.
    • units: 64. Número de neuronas en la capa LSTM.
    • return_sequences: False. True/False. Determina si una capa LSTM devuelve la secuencia completa de salidas o solo la última; 'True' devuelve toda la secuencia, 'False' solo la última salida.
    • activation: 'relu'. Función de activación utilizada en la capa LSTM.
    • kernel_regularizer: L1L2 (0.001 y 0.001). Aplica penalizaciones a los coeficientes de la capa durante el entrenamiento, en este caso utilizando la regularización L1 y L2, lo que puede ayudar a prevenir el sobreajuste.
    • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
    • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
    • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
    • optimizer: 'SGD'. Optimizador utilizado para
    • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
    • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
    • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
    • epochs: 20. Número máximo de épocas para entrenar el modelo.

Configuración de hiperparámetros CRNN:

  • CRNN_1:
    • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
    • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
    • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
    • units (Conv1D): 32. Número de neuronas en la capa CRNN.
    • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
    • activation: 'relu'. Función de activación utilizada en la capa CRNN.
    • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
    • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
    • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
    • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
    • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
    • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
    • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
    • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
    • epochs: 20. Número máximo de épocas para entrenar el modelo.
  • CRNN_2:
    • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
    • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
    • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
    • units (Conv1D): 64. Número de neuronas en la capa CRNN.
    • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
    • activation: 'relu'. Función de activación utilizada en la capa CRNN.
    • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
    • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
    • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
    • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
    • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
    • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
    • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
    • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
    • epochs: 20. Número máximo de épocas para entrenar el modelo.
  • CRNN_3:
    • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
    • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
    • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
    • units (Conv1D): 32. Número de neuronas en la capa CRNN.
    • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
    • activation: 'relu'. Función de activación utilizada en la capa CRNN.
    • kernel_regularizer: L1L2 (0.001 y 0.001).
    • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
    • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
    • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
    • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
    • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
    • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
    • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
    • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
    • optimizer: 'SGD'. Optimizador utilizado para
    • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
    • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
    • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
    • epochs: 20. Número máximo de épocas para entrenar el modelo.

Mostrar los 5 pacientes con mayor número de mediciones desde el inicio hasta el final del estudio y su cantidad de mediciones totales junto su media total¶

In [69]:
# Agrupar el DataFrame 'df' por el ID de paciente ('Patient_ID') y contar el número de mediciones ('Measurement_time') que cada paciente ha realizado.
# Luego ordenar los resultados de manera descendente ('sort_values(ascending=False)') y seleccionar los primeros 5 pacientes con más mediciones ('head(5)').
top_5_pacientes = df.groupby('Patient_ID')['Measurement_time'].count().sort_values(ascending=False).head(5)

# Crear un nuevo DataFrame 'df_top_5_pacientes_modificado' que contiene los mismos datos que 'top_5_pacientes',
# pero reseteamos el índice para que el ID de paciente y el número de mediciones estén en columnas separadas.
df_top_5_pacientes_modificado = top_5_pacientes.reset_index()

# Renombrar las columnas del DataFrame 'df_top_5_pacientes_modificado'.
df_top_5_pacientes_modificado.columns = ['Patient_ID', 'Num_Mediciones']

# Agrupar el DataFrame 'df' por el ID de paciente ('Patient_ID') y obtener la fecha mínima ('min') y máxima ('max') de las mediciones.
# Luego resetear el índice y se guarda en el DataFrame 'fechas_min_max'.
fechas_min_max = df.groupby('Patient_ID')['Measurement_date'].agg(['min', 'max']).reset_index()

# Unir el DataFrame 'df_top_5_pacientes_modificado' y el DataFrame 'fechas_min_max' en una nueva DataFrame 'df_top_5_pacientes_modificado'
# Utilizar el ID de paciente ('Patient_ID') como clave de unión.
df_top_5_pacientes_modificado = df_top_5_pacientes_modificado.merge(fechas_min_max, on='Patient_ID')

# Agrupar el DataFrame 'df' por el ID de paciente ('Patient_ID') y calcular la media de la columna 'Measurement' para cada paciente
media_glucosa = df.groupby('Patient_ID')['Measurement'].mean()

# Crear un nuevo DataFrame 'df_media_glucosa' que contiene los mismos datos que 'media_glucosa',
# pero reseteamos el índice para que el ID de paciente y la media de glucosa estén en columnas separadas.
df_media_glucosa = media_glucosa.reset_index()

# Renombrar las columnas del DataFrame 'df_media_glucosa'.
df_media_glucosa.columns = ['Patient_ID', 'Media_Glucosa']

# Unir el DataFrame 'df_top_5_pacientes_modificado' y el DataFrame 'df_media_glucosa' en una nueva DataFrame 'df_top_5_pacientes_modificado'
# Utilizar el ID de paciente ('Patient_ID') como clave de unión.
df_top_5_pacientes_modificado = df_top_5_pacientes_modificado.merge(df_media_glucosa, on='Patient_ID')
df_top_5_pacientes_modificado
Out[69]:
Patient_ID Num_Mediciones min max Media_Glucosa
0 24 11987 2018-07-13 2022-03-12 151.588888
1 11 11332 2018-06-12 2022-02-25 150.851747
2 22 10409 2018-11-06 2022-03-15 157.267653
3 83 9198 2018-02-21 2022-03-14 144.642314
4 92 8509 2018-06-06 2022-01-10 152.568692

Experimento con los 5 pacientes con mayor número de mediciones de glucosa¶

Paciente 22
¶

La comparativa extensa de soluciones con los modelos RNN y CRNN se va a realizar para el paciente 22 debido a que tras un análisis extenso del paciente 22 se ha detectado que es el que prácticamente tiene más mediciones y es el que tiene un mayor número de mediciones en un mes, lo cual facilita al algoritmo en descubrir patrones en los datos.

Posteriormente a la comparativa de soluciones del paciente 22, se llevará a cabo más comparativas de soluciones con los modelos RNN y CRNN pero con el resto de los 5 pacientes con más mediciones. De esta manera se podrá confirmar que el experimento se puede llevar a cabo de manera personalizada para cada uno de los pacientes.

Red Neuronal Recurrente (RNN) con el algoritmo Long Short-Term Memory (LSTM)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo LSTM con características de entrada: 'Measurement'¶

En este caso se esta entrenando al algoritmo LSTM con la variable 'Measurement'.

  • 'Mesurement' es el nivel de glucosa.

LSTM_1 (Mejor resultado)¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [70]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.layers import Dropout
from tensorflow.keras.callbacks import EarlyStopping

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

2. Dividir el conjunto de datos¶

In [71]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [72]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement']], seq_length)
X_val, y_val = create_sequences(val[['Measurement']], seq_length)
X_test, y_test = create_sequences(test[['Measurement']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 1))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], 1)))
model.add(Dense(1))
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm (LSTM)                 (None, 32)                4352      
                                                                 
 dense (Dense)               (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,385
Trainable params: 4,385
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [73]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 6s 3ms/step - loss: 134.4286 - val_loss: 121.5171
Epoch 2/20
1471/1471 [==============================] - 4s 3ms/step - loss: 101.5501 - val_loss: 90.2153
Epoch 3/20
1471/1471 [==============================] - 4s 3ms/step - loss: 70.5913 - val_loss: 59.3573
Epoch 4/20
1471/1471 [==============================] - 4s 3ms/step - loss: 40.5646 - val_loss: 29.0356
Epoch 5/20
1471/1471 [==============================] - 4s 3ms/step - loss: 19.4058 - val_loss: 11.4876
Epoch 6/20
1471/1471 [==============================] - 4s 3ms/step - loss: 12.4145 - val_loss: 7.2027
Epoch 7/20
1471/1471 [==============================] - 4s 3ms/step - loss: 7.9547 - val_loss: 3.7864
Epoch 8/20
1471/1471 [==============================] - 4s 3ms/step - loss: 5.6591 - val_loss: 2.1678
Epoch 9/20
1471/1471 [==============================] - 4s 3ms/step - loss: 4.3217 - val_loss: 1.6510
Epoch 10/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.6444 - val_loss: 1.2388
Epoch 11/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.1234 - val_loss: 0.5408
Epoch 12/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.8459 - val_loss: 1.2027
Epoch 13/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.6973 - val_loss: 1.0889
Epoch 14/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.6017 - val_loss: 0.6839

5. Evaluar¶

In [74]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_1 = train_eval
val_eval_measurement_1 = val_eval
test_eval_measurement_1 = test_eval

train_rmse_measurement_1 = train_rmse
val_rmse_measurement_1 = val_rmse
test_rmse_measurement_1 = test_rmse
2942/2942 [==============================] - 3s 1ms/step
368/368 [==============================] - 0s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 2.5317225456237793
Evaluación Validación MAE: 0.6839110851287842
Evaluación Prueba MAE: 1.7721638679504395
Entrenamiento RMSE: 7.2636284828186035
Validación RMSE: 1.6599440574645996
Prueba RMSE: 2.98423433303833

6. Gráfica¶

In [75]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [76]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df['Measurement'].values[-seq_length:].astype('float32')
for i in range(1):  # Rango a 1 para predecir solo los próximos 15 minutos
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 1))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], next_prediction)

# Crear fechas para los próximos 15 minutos y el número de iteraciones "1" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 45ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00       151.368362

Distintas configuraciones de hiperparámetros probadas¶

LSTM_2¶

In [77]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement']], seq_length)
X_val, y_val = create_sequences(val[['Measurement']], seq_length)
X_test, y_test = create_sequences(test[['Measurement']], seq_length)

# 13. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 1))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1))

# 14. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_2 = train_eval
val_eval_measurement_2 = val_eval
test_eval_measurement_2 = test_eval

train_rmse_measurement_2 = train_rmse
val_rmse_measurement_2 = val_rmse
test_rmse_measurement_2 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_1 (LSTM)               (None, 64)                16896     
                                                                 
 dense_1 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 16,961
Trainable params: 16,961
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 6s 4ms/step - loss: 4.5104 - val_loss: 1.0462
Epoch 2/20
1471/1471 [==============================] - 5s 4ms/step - loss: 2.4175 - val_loss: 1.2524
Epoch 3/20
1471/1471 [==============================] - 5s 4ms/step - loss: 2.1441 - val_loss: 0.9971
Epoch 4/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.1509 - val_loss: 1.1188
Epoch 5/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.2449 - val_loss: 1.1237
Epoch 6/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.0986 - val_loss: 1.0287
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.820347547531128
Evaluación Validación MAE: 1.028662085533142
Evaluación Prueba MAE: 1.3249800205230713
Entrenamiento RMSE: 5.082148551940918
Validación RMSE: 1.5881013870239258
Prueba RMSE: 1.8096578121185303

LSTM_3¶

In [78]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement']], seq_length)
X_val, y_val = create_sequences(val[['Measurement']], seq_length)
X_test, y_test = create_sequences(test[['Measurement']], seq_length)

# 13. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 1))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1))

# 14. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1))
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_3 = train_eval
val_eval_measurement_3 = val_eval
test_eval_measurement_3 = test_eval

train_rmse_measurement_3 = train_rmse
val_rmse_measurement_3 = val_rmse
test_rmse_measurement_3 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_2 (LSTM)               (None, 8, 32)             4352      
                                                                 
 batch_normalization (BatchN  (None, 8, 32)            128       
 ormalization)                                                   
                                                                 
 dropout (Dropout)           (None, 8, 32)             0         
                                                                 
 lstm_3 (LSTM)               (None, 64)                24832     
                                                                 
 batch_normalization_1 (Batc  (None, 64)               256       
 hNormalization)                                                 
                                                                 
 dropout_1 (Dropout)         (None, 64)                0         
                                                                 
 dense_2 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,633
Trainable params: 29,441
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 11s 7ms/step - loss: 46.8739 - val_loss: 4.1575
Epoch 2/20
1471/1471 [==============================] - 9s 6ms/step - loss: 22.2008 - val_loss: 6.2402
Epoch 3/20
1471/1471 [==============================] - 9s 6ms/step - loss: 21.6964 - val_loss: 3.0885
Epoch 4/20
1471/1471 [==============================] - 9s 6ms/step - loss: 21.3211 - val_loss: 9.5193
Epoch 5/20
1471/1471 [==============================] - 9s 6ms/step - loss: 21.7168 - val_loss: 3.4406
Epoch 6/20
1471/1471 [==============================] - 10s 6ms/step - loss: 21.4691 - val_loss: 4.0922
2942/2942 [==============================] - 6s 2ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 7.282508373260498
Evaluación Validación MAE: 4.0922064781188965
Evaluación Prueba MAE: 6.770111560821533
Entrenamiento RMSE: 10.493366241455078
Validación RMSE: 4.704648017883301
Prueba RMSE: 6.8991193771362305

Algoritmo LSTM con características de entrada: 'Measurement' y 'In_Range'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Measurement' y 'In_Range'.

  • 'Mesurement' es en nivel de glucosa.
  • 'In_Range' indica si una medición está dentro del rango de niveles normales, entre 70-180 mg/dl.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [431]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'In_Range']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [432]:
df
Out[432]:
Measurement In_Range
Datetime
2018-11-06 06:45:00 134 True
2018-11-06 07:00:00 134 True
2018-11-06 07:15:00 139 True
2018-11-06 07:30:00 145 True
2018-11-06 07:45:00 147 True
... ... ...
2022-03-08 11:30:00 179 True
2022-03-08 12:30:00 173 True
2022-03-08 12:45:00 171 True
2022-03-08 13:15:00 165 True
2022-03-15 23:46:00 155 True

10409 rows × 2 columns

2. Dividir el conjunto de datos¶

In [433]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [434]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','In_Range']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','In_Range']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','In_Range']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_58"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_68 (LSTM)              (None, 64)                17152     
                                                                 
 dense_58 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,217
Trainable params: 17,217
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [435]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 10s 6ms/step - loss: 3.9862 - val_loss: 1.6950
Epoch 2/20
1471/1471 [==============================] - 9s 6ms/step - loss: 2.6799 - val_loss: 1.6361
Epoch 3/20
1471/1471 [==============================] - 9s 6ms/step - loss: 2.6210 - val_loss: 0.7923
Epoch 4/20
1471/1471 [==============================] - 9s 6ms/step - loss: 2.3982 - val_loss: 1.2908
Epoch 5/20
1471/1471 [==============================] - 9s 6ms/step - loss: 2.0989 - val_loss: 0.3290
Epoch 6/20
1471/1471 [==============================] - 9s 6ms/step - loss: 1.8890 - val_loss: 0.3713
Epoch 7/20
1471/1471 [==============================] - 9s 6ms/step - loss: 2.0137 - val_loss: 0.3376
Epoch 8/20
1471/1471 [==============================] - 9s 6ms/step - loss: 1.6471 - val_loss: 0.9705

5. Evaluar¶

In [436]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_in_range_2 = train_eval
val_eval_measurement_in_range_2 = val_eval
test_eval_measurement_in_range_2 = test_eval

train_rmse_measurement_in_range_2 = train_rmse
val_rmse_measurement_in_range_2 = val_rmse
test_rmse_measurement_in_range_2 = test_rmse
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 1.7063654661178589
Evaluación Validación MAE: 0.9705080986022949
Evaluación Prueba MAE: 0.9188098907470703
Entrenamiento RMSE: 4.77381706237793
Validación RMSE: 1.4949880838394165
Prueba RMSE: 1.4915213584899902

6. Gráfica¶

In [437]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [438]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement','In_Range']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 2))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1]]], axis=0)

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 14ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00       155.457123

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [87]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'In_Range']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','In_Range']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','In_Range']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','In_Range']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_in_range_1 = train_eval
val_eval_measurement_in_range_1 = val_eval
test_eval_measurement_in_range_1 = test_eval

train_rmse_measurement_in_range_1 = train_rmse
val_rmse_measurement_in_range_1 = val_rmse
test_rmse_measurement_in_range_1 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_5 (LSTM)               (None, 32)                4480      
                                                                 
 dense_4 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,513
Trainable params: 4,513
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 6s 3ms/step - loss: 140.2919 - val_loss: 128.8257
Epoch 2/20
1471/1471 [==============================] - 4s 3ms/step - loss: 109.1032 - val_loss: 98.6539
Epoch 3/20
1471/1471 [==============================] - 4s 3ms/step - loss: 80.2873 - val_loss: 70.4641
Epoch 4/20
1471/1471 [==============================] - 4s 3ms/step - loss: 52.5715 - val_loss: 42.6706
Epoch 5/20
1471/1471 [==============================] - 4s 3ms/step - loss: 27.8966 - val_loss: 18.1041
Epoch 6/20
1471/1471 [==============================] - 4s 3ms/step - loss: 15.1207 - val_loss: 8.1287
Epoch 7/20
1471/1471 [==============================] - 4s 3ms/step - loss: 9.0547 - val_loss: 4.6876
Epoch 8/20
1471/1471 [==============================] - 4s 3ms/step - loss: 6.3764 - val_loss: 2.3348
Epoch 9/20
1471/1471 [==============================] - 4s 3ms/step - loss: 4.7016 - val_loss: 1.4285
Epoch 10/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.9548 - val_loss: 1.5147
Epoch 11/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.4314 - val_loss: 1.7633
Epoch 12/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.1001 - val_loss: 0.9398
Epoch 13/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.8262 - val_loss: 0.8950
Epoch 14/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.5794 - val_loss: 0.8000
Epoch 15/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.3661 - val_loss: 0.7080
Epoch 16/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.3892 - val_loss: 0.7625
Epoch 17/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2026 - val_loss: 0.8682
Epoch 18/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.0630 - val_loss: 0.5444
Epoch 19/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.0429 - val_loss: 0.4265
Epoch 20/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.1417 - val_loss: 0.6156
2942/2942 [==============================] - 3s 1ms/step
368/368 [==============================] - 0s 1ms/step
368/368 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 1.9562634229660034
Evaluación Validación MAE: 0.6155513525009155
Evaluación Prueba MAE: 1.1810508966445923
Entrenamiento RMSE: 6.228616237640381
Validación RMSE: 1.4747318029403687
Prueba RMSE: 2.0733773708343506

LSTM_3¶

In [88]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'In_Range']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','In_Range']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','In_Range']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','In_Range']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_in_range_3 = train_eval
val_eval_measurement_in_range_3 = val_eval
test_eval_measurement_in_range_3 = test_eval

train_rmse_measurement_in_range_3 = train_rmse
val_rmse_measurement_in_range_3 = val_rmse
test_rmse_measurement_in_range_3 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_5"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_6 (LSTM)               (None, 8, 32)             4480      
                                                                 
 batch_normalization_2 (Batc  (None, 8, 32)            128       
 hNormalization)                                                 
                                                                 
 dropout_2 (Dropout)         (None, 8, 32)             0         
                                                                 
 lstm_7 (LSTM)               (None, 64)                24832     
                                                                 
 batch_normalization_3 (Batc  (None, 64)               256       
 hNormalization)                                                 
                                                                 
 dropout_3 (Dropout)         (None, 64)                0         
                                                                 
 dense_5 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,761
Trainable params: 29,569
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 13s 7ms/step - loss: 46.7474 - val_loss: 4.5680
Epoch 2/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.9981 - val_loss: 2.9417
Epoch 3/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.3113 - val_loss: 5.7861
Epoch 4/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.1704 - val_loss: 2.8970
Epoch 5/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.9418 - val_loss: 2.2306
Epoch 6/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.7462 - val_loss: 3.0171
Epoch 7/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.6117 - val_loss: 4.5035
Epoch 8/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.5307 - val_loss: 2.0329
Epoch 9/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.3379 - val_loss: 3.3944
Epoch 10/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.2784 - val_loss: 2.7354
Epoch 11/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.2344 - val_loss: 2.1492
2942/2942 [==============================] - 6s 2ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 4.057898044586182
Evaluación Validación MAE: 2.1491730213165283
Evaluación Prueba MAE: 3.895164728164673
Entrenamiento RMSE: 6.431542873382568
Validación RMSE: 2.7562716007232666
Prueba RMSE: 4.294253349304199

Algoritmo LSTM con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Mesurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hyperglycemia" indica si una medición está por encima del rango normal.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [366]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [367]:
df
Out[367]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-11-06 06:45:00 134 False False
2018-11-06 07:00:00 134 False False
2018-11-06 07:15:00 139 False False
2018-11-06 07:30:00 145 False False
2018-11-06 07:45:00 147 False False
... ... ... ...
2022-03-08 11:30:00 179 False False
2022-03-08 12:30:00 173 False False
2022-03-08 12:45:00 171 False False
2022-03-08 13:15:00 165 False False
2022-03-15 23:46:00 155 False False

10409 rows × 3 columns

2. Dividir el conjunto de datos¶

In [368]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [369]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_51"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_61 (LSTM)              (None, 64)                17408     
                                                                 
 dense_51 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,473
Trainable params: 17,473
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [370]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 12s 7ms/step - loss: 5.6162 - val_loss: 3.7250
Epoch 2/20
1471/1471 [==============================] - 10s 7ms/step - loss: 2.6998 - val_loss: 1.0888
Epoch 3/20
1471/1471 [==============================] - 10s 7ms/step - loss: 2.4279 - val_loss: 1.4075
Epoch 4/20
1471/1471 [==============================] - 10s 7ms/step - loss: 2.1701 - val_loss: 0.3023
Epoch 5/20
1471/1471 [==============================] - 10s 7ms/step - loss: 2.1071 - val_loss: 1.2181
Epoch 6/20
1471/1471 [==============================] - 10s 7ms/step - loss: 1.8119 - val_loss: 0.4553
Epoch 7/20
1471/1471 [==============================] - 10s 7ms/step - loss: 1.6434 - val_loss: 0.3886

5. Evaluar¶

In [371]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_2 = train_eval
val_eval_measurement_hypo_hype_2 = val_eval
test_eval_measurement_hypo_hype_2 = test_eval

train_rmse_measurement_hypo_hype_2 = train_rmse
val_rmse_measurement_hypo_hype_2 = val_rmse
test_rmse_measurement_hypo_hype_2 = test_rmse
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 1ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 1.2353506088256836
Evaluación Validación MAE: 0.3886474668979645
Evaluación Prueba MAE: 0.29393190145492554
Entrenamiento RMSE: 4.759490489959717
Validación RMSE: 1.2376997470855713
Prueba RMSE: 1.244410753250122

6. Gráfica¶

In [372]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [373]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')

# Almacena dato
future_predictions_hypo_hype_2_LSTM_22 = future_predictions
plt.show()
1/1 [==============================] - 0s 14ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00       157.521851

6.2 Clarke Error Grid¶

In [374]:
def clarke_error_grid(ref_values, pred_values):
    assert (len(ref_values) == len(pred_values)), "Unequal number of values (reference: {}) (prediction: {}).".format(len(ref_values), len(pred_values))

    if max(ref_values) > 400 or max(pred_values) > 400:
        print("Input Warning: the maximum reference value {} or the maximum prediction value {} exceeds the normal physiological range of glucose (<400 mg/dl).".format(max(ref_values), max(pred_values)))
    if min(ref_values) < 0 or min(pred_values) < 0:
        print("Input Warning: the minimum reference value {} or the minimum prediction value {} is less than 0 mg/dl.".format(min(ref_values),  min(pred_values)))

    plt.clf()
    plt.scatter(ref_values, pred_values, marker='o', color='black', s=8)
    plt.title("Clarke Error Grid")
    plt.xlabel("Reference Concentration (mg/dl)")
    plt.ylabel("Prediction Concentration (mg/dl)")
    plt.xticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.yticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.gca().set_facecolor('white')
    plt.gca().set_xlim([0, 400])
    plt.gca().set_ylim([0, 400])
    plt.gca().set_aspect((400)/(400))
    plt.plot([0, 400], [0, 400], ':', c='black')
    plt.plot([0, 175/3], [70, 70], '-', c='black')
    plt.plot([175/3, 400/1.2], [70, 400], '-', c='black')
    plt.plot([70, 70], [84, 400],'-', c='black')
    plt.plot([0, 70], [180, 180], '-', c='black')
    plt.plot([70, 290], [180, 400],'-', c='black')
    plt.plot([70, 70], [0, 56], '-', c='black')
    plt.plot([70, 400], [56, 320],'-', c='black')
    plt.plot([180, 180], [0, 70], '-', c='black')
    plt.plot([180, 400], [70, 70], '-', c='black')
    plt.plot([240, 240], [70, 180],'-', c='black')
    plt.plot([240, 400], [180, 180], '-', c='black')
    plt.plot([130, 180], [0, 70], '-', c='black')
    plt.text(30, 15, "A", fontsize=15)
    plt.text(370, 260, "B", fontsize=15)
    plt.text(280, 370, "B", fontsize=15)
    plt.text(160, 370, "C", fontsize=15)
    plt.text(160, 15, "C", fontsize=15)
    plt.text(30, 140, "D", fontsize=15)
    plt.text(370, 120, "D", fontsize=15)
    plt.text(30, 370, "E", fontsize=15)
    plt.text(370, 15, "E", fontsize=15)

    zone = [0] * 5
    for i in range(len(ref_values)):
        if (ref_values[i] <= 70 and pred_values[i] <= 70) or (pred_values[i] <= 1.2 * ref_values[i] and pred_values[i] >= 0.8 * ref_values[i]):
            zone[0] += 1  # Zone A
        elif (ref_values[i] >= 180 and pred_values[i] <= 70) or (ref_values[i] <= 70 and pred_values[i] >= 180):
            zone[4] += 1  # Zone E
        elif ((ref_values[i] >= 70 and ref_values[i] <= 290) and pred_values[i] >= ref_values[i] + 110) or ((ref_values[i] >= 130 and ref_values[i] <= 180) and (pred_values[i] <= (7/5) * ref_values[i] - 182)):
            zone[2] += 1  # Zone C
        elif (ref_values[i] >= 240 and (pred_values[i] >= 70 and pred_values[i] <= 180)) or (ref_values[i] <= 175/3 and pred_values[i] <= 180 and pred_values[i] >= 70) or ((ref_values[i] >= 175/3 and ref_values[i] <= 70) and pred_values[i] >= (6/5) * ref_values[i]):
            zone[3] += 1  # Zone D
        else:
            zone[1] += 1  # Zone B

    return plt, zone

ref_values = y_test
pred_values = y_pred_test

plt, zone = clarke_error_grid(ref_values, pred_values)

# Asignar la figura a una variable antes de llamar a plt.show()
fig_hypo_hype_2_LSTM_22 = plt.gcf()

# Mostrar la figura
plt.show()

# Crear DataFrame para visualizar el conteo de zonas
zone_df = pd.DataFrame({'Zona': ['A', 'B', 'C', 'D', 'E'], 'Conteo': zone})

# Calcular la proporción de cada zona respecto al total
total = sum(zone)
zone_df['Proporción'] = zone_df['Conteo'] / total

# Mostrar la proporción en porcentaje
zone_df['Proporción'] = zone_df['Proporción'].apply(lambda x: '{:.2f}%'.format(x * 100))

# Asignar el DataFrame a una variable
zone_df_hypo_hype_2_LSTM_22 = zone_df

# Ocultar el índice y mostrar el DataFrame
print("Conteo de Zonas:")
display(zone_df_hypo_hype_2_LSTM_22.style.hide(axis="index"))
Conteo de Zonas:
Zona Conteo Proporción
A 11758 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [98]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_1 = train_eval
val_eval_measurement_hypo_hype_1 = val_eval
test_eval_measurement_hypo_hype_1 = test_eval

train_rmse_measurement_hypo_hype_1 = train_rmse
val_rmse_measurement_hypo_hype_1 = val_rmse
test_rmse_measurement_hypo_hype_1 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_7"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_9 (LSTM)               (None, 32)                4608      
                                                                 
 dense_7 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,641
Trainable params: 4,641
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 6s 3ms/step - loss: 140.2028 - val_loss: 130.6727
Epoch 2/20
1471/1471 [==============================] - 4s 3ms/step - loss: 113.3757 - val_loss: 104.8696
Epoch 3/20
1471/1471 [==============================] - 4s 3ms/step - loss: 88.0383 - val_loss: 79.7525
Epoch 4/20
1471/1471 [==============================] - 4s 3ms/step - loss: 63.2013 - val_loss: 54.8701
Epoch 5/20
1471/1471 [==============================] - 4s 3ms/step - loss: 39.2060 - val_loss: 30.5484
Epoch 6/20
1471/1471 [==============================] - 4s 3ms/step - loss: 21.0255 - val_loss: 12.9620
Epoch 7/20
1471/1471 [==============================] - 4s 3ms/step - loss: 13.0656 - val_loss: 7.4856
Epoch 8/20
1471/1471 [==============================] - 4s 3ms/step - loss: 9.0737 - val_loss: 4.8913
Epoch 9/20
1471/1471 [==============================] - 4s 3ms/step - loss: 6.9951 - val_loss: 3.5623
Epoch 10/20
1471/1471 [==============================] - 4s 3ms/step - loss: 5.4142 - val_loss: 2.0784
Epoch 11/20
1471/1471 [==============================] - 4s 3ms/step - loss: 4.1992 - val_loss: 1.4400
Epoch 12/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.5072 - val_loss: 1.0342
Epoch 13/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.1931 - val_loss: 1.0726
Epoch 14/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.8773 - val_loss: 0.6980
Epoch 15/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.6010 - val_loss: 0.7250
Epoch 16/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.4358 - val_loss: 0.7231
Epoch 17/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.3550 - val_loss: 0.5968
Epoch 18/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.3171 - val_loss: 0.4062
Epoch 19/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2587 - val_loss: 0.4825
Epoch 20/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.0744 - val_loss: 0.6296
2942/2942 [==============================] - 3s 1ms/step
368/368 [==============================] - 0s 1ms/step
368/368 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 2.0523781776428223
Evaluación Validación MAE: 0.6295686960220337
Evaluación Prueba MAE: 1.2685140371322632
Entrenamiento RMSE: 6.3875732421875
Validación RMSE: 1.4537099599838257
Prueba RMSE: 2.2100160121917725

LSTM_3¶

In [99]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt


# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3 = train_eval
val_eval_measurement_hypo_hype_3 = val_eval
test_eval_measurement_hypo_hype_3 = test_eval

train_rmse_measurement_hypo_hype_3 = train_rmse
val_rmse_measurement_hypo_hype_3 = val_rmse
test_rmse_measurement_hypo_hype_3 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_8"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_10 (LSTM)              (None, 8, 32)             4608      
                                                                 
 batch_normalization_4 (Batc  (None, 8, 32)            128       
 hNormalization)                                                 
                                                                 
 dropout_4 (Dropout)         (None, 8, 32)             0         
                                                                 
 lstm_11 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_5 (Batc  (None, 64)               256       
 hNormalization)                                                 
                                                                 
 dropout_5 (Dropout)         (None, 64)                0         
                                                                 
 dense_8 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,889
Trainable params: 29,697
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 14s 8ms/step - loss: 45.9336 - val_loss: 6.0350
Epoch 2/20
1471/1471 [==============================] - 10s 7ms/step - loss: 22.5931 - val_loss: 16.0798
Epoch 3/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.7881 - val_loss: 7.9685
Epoch 4/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.7075 - val_loss: 4.8527
Epoch 5/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.6124 - val_loss: 3.3491
Epoch 6/20
1471/1471 [==============================] - 11s 7ms/step - loss: 21.2566 - val_loss: 15.1079
Epoch 7/20
1471/1471 [==============================] - 11s 7ms/step - loss: 21.1486 - val_loss: 6.0739
Epoch 8/20
1471/1471 [==============================] - 11s 7ms/step - loss: 21.1002 - val_loss: 3.1232
Epoch 9/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.8486 - val_loss: 15.2919
Epoch 10/20
1471/1471 [==============================] - 11s 7ms/step - loss: 20.7623 - val_loss: 3.5234
Epoch 11/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.6614 - val_loss: 9.9177
2942/2942 [==============================] - 6s 2ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 9.994305610656738
Evaluación Validación MAE: 9.917738914489746
Evaluación Prueba MAE: 15.304161071777344
Entrenamiento RMSE: 12.99858283996582
Validación RMSE: 11.009345054626465
Prueba RMSE: 16.2338924407959

Algoritmo LSTM con características de entrada: 'Measurement' y 'Hour_of_Day'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Measurement' y 'Hour_of_Day'.

  • 'Mesurement' es el nivel de glucosa.
  • 'Hour_of_Day' para indicar la hora del día en que se tomó cada medición.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [100]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hour_of_Day']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [101]:
df
Out[101]:
Measurement Hour_of_Day
Datetime
2018-11-06 06:45:00 134 6
2018-11-06 07:00:00 134 7
2018-11-06 07:15:00 139 7
2018-11-06 07:30:00 145 7
2018-11-06 07:45:00 147 7
... ... ...
2022-03-08 11:30:00 179 11
2022-03-08 12:30:00 173 12
2022-03-08 12:45:00 171 12
2022-03-08 13:15:00 165 13
2022-03-15 23:46:00 155 23

10409 rows × 2 columns

2. Dividir el conjunto de datos¶

In [102]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [103]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','Hour_of_Day']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','Hour_of_Day']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','Hour_of_Day']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_9"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_12 (LSTM)              (None, 64)                17152     
                                                                 
 dense_9 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,217
Trainable params: 17,217
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [104]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 9s 5ms/step - loss: 4.4482 - val_loss: 2.0499
Epoch 2/20
1471/1471 [==============================] - 7s 5ms/step - loss: 2.4398 - val_loss: 1.6714
Epoch 3/20
1471/1471 [==============================] - 7s 5ms/step - loss: 2.3849 - val_loss: 0.3446
Epoch 4/20
1471/1471 [==============================] - 7s 4ms/step - loss: 1.8552 - val_loss: 0.7677
Epoch 5/20
1471/1471 [==============================] - 7s 5ms/step - loss: 2.0796 - val_loss: 0.6398
Epoch 6/20
1471/1471 [==============================] - 7s 5ms/step - loss: 2.0748 - val_loss: 0.9001

5. Evaluar¶

In [105]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hour_of_day_2 = train_eval
val_eval_measurement_hour_of_day_2 = val_eval
test_eval_measurement_hour_of_day_2 = test_eval

train_rmse_measurement_hour_of_day_2 = train_rmse
val_rmse_measurement_hour_of_day_2 = val_rmse
test_rmse_measurement_hour_of_day_2 = test_rmse
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.8424052000045776
Evaluación Validación MAE: 0.9001190662384033
Evaluación Prueba MAE: 0.5775524973869324
Entrenamiento RMSE: 5.213829517364502
Validación RMSE: 1.5117509365081787
Prueba RMSE: 1.3892979621887207

6. Gráfica¶

In [106]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [107]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement','Hour_of_Day']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 2))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1]]], axis=0)

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 26ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00        158.36853

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [108]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hour_of_Day']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','Hour_of_Day']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','Hour_of_Day']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','Hour_of_Day']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hour_of_day_1 = train_eval
val_eval_measurement_hour_of_day_1 = val_eval
test_eval_measurement_hour_of_day_1 = test_eval

train_rmse_measurement_hour_of_day_1 = train_rmse
val_rmse_measurement_hour_of_day_1 = val_rmse
test_rmse_measurement_hour_of_day_1 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_10"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_13 (LSTM)              (None, 32)                4480      
                                                                 
 dense_10 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,513
Trainable params: 4,513
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 6s 3ms/step - loss: 141.8649 - val_loss: 134.3838
Epoch 2/20
1471/1471 [==============================] - 4s 3ms/step - loss: 118.1938 - val_loss: 110.8881
Epoch 3/20
1471/1471 [==============================] - 4s 3ms/step - loss: 91.4341 - val_loss: 81.0553
Epoch 4/20
1471/1471 [==============================] - 4s 3ms/step - loss: 63.4834 - val_loss: 54.2606
Epoch 5/20
1471/1471 [==============================] - 4s 3ms/step - loss: 38.0505 - val_loss: 28.6272
Epoch 6/20
1471/1471 [==============================] - 4s 3ms/step - loss: 19.1333 - val_loss: 10.9875
Epoch 7/20
1471/1471 [==============================] - 4s 3ms/step - loss: 13.1449 - val_loss: 7.5980
Epoch 8/20
1471/1471 [==============================] - 4s 3ms/step - loss: 8.3345 - val_loss: 5.7351
Epoch 9/20
1471/1471 [==============================] - 4s 3ms/step - loss: 5.8487 - val_loss: 2.9842
Epoch 10/20
1471/1471 [==============================] - 4s 3ms/step - loss: 4.8431 - val_loss: 2.1161
Epoch 11/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.8786 - val_loss: 1.1025
Epoch 12/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.4309 - val_loss: 1.5722
Epoch 13/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.2637 - val_loss: 1.2226
Epoch 14/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.8449 - val_loss: 0.6205
Epoch 15/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.7219 - val_loss: 0.8286
Epoch 16/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.7860 - val_loss: 1.6312
Epoch 17/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.4587 - val_loss: 0.4950
Epoch 18/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.4047 - val_loss: 0.4798
Epoch 19/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.1819 - val_loss: 0.8118
Epoch 20/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2835 - val_loss: 1.1068
2942/2942 [==============================] - 3s 1ms/step
368/368 [==============================] - 0s 1ms/step
368/368 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 2.790247678756714
Evaluación Validación MAE: 1.106804370880127
Evaluación Prueba MAE: 2.241769552230835
Entrenamiento RMSE: 6.70780086517334
Validación RMSE: 1.9232988357543945
Prueba RMSE: 3.132213592529297

LSTM_3¶

In [109]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hour_of_Day']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','Hour_of_Day']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','Hour_of_Day']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','Hour_of_Day']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hour_of_day_3 = train_eval
val_eval_measurement_hour_of_day_3 = val_eval
test_eval_measurement_hour_of_day_3 = test_eval

train_rmse_measurement_hour_of_day_3 = train_rmse
val_rmse_measurement_hour_of_day_3 = val_rmse
test_rmse_measurement_hour_of_day_3 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_11"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_14 (LSTM)              (None, 8, 32)             4480      
                                                                 
 batch_normalization_6 (Batc  (None, 8, 32)            128       
 hNormalization)                                                 
                                                                 
 dropout_6 (Dropout)         (None, 8, 32)             0         
                                                                 
 lstm_15 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_7 (Batc  (None, 64)               256       
 hNormalization)                                                 
                                                                 
 dropout_7 (Dropout)         (None, 64)                0         
                                                                 
 dense_11 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,761
Trainable params: 29,569
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 13s 7ms/step - loss: 45.6855 - val_loss: 10.5521
Epoch 2/20
1471/1471 [==============================] - 10s 7ms/step - loss: 23.1582 - val_loss: 11.9310
Epoch 3/20
1471/1471 [==============================] - 10s 7ms/step - loss: 22.1564 - val_loss: 4.8221
Epoch 4/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.6581 - val_loss: 4.9306
Epoch 5/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.1379 - val_loss: 1.8938
Epoch 6/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.9584 - val_loss: 4.5947
Epoch 7/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.7995 - val_loss: 1.6903
Epoch 8/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.6777 - val_loss: 3.1862
Epoch 9/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.6014 - val_loss: 2.3855
Epoch 10/20
1471/1471 [==============================] - 10s 7ms/step - loss: 20.5678 - val_loss: 6.1823
2942/2942 [==============================] - 6s 2ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 6.899686336517334
Evaluación Validación MAE: 6.18228816986084
Evaluación Prueba MAE: 10.067994117736816
Entrenamiento RMSE: 9.938611030578613
Validación RMSE: 7.107998847961426
Prueba RMSE: 10.758072853088379

Algoritmo LSTM con características de entrada: 'Measurement' y 'Day_of_Week'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Measurement' y 'Day_of_Week'.

  • 'Mesurement' es el nivel de glucosa.
  • 'Day_of_Week' para indicar el día de la semana. 0 es lunes y 6 es domingo.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [110]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Day_of_Week']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [111]:
df
Out[111]:
Measurement Day_of_Week
Datetime
2018-11-06 06:45:00 134 1
2018-11-06 07:00:00 134 1
2018-11-06 07:15:00 139 1
2018-11-06 07:30:00 145 1
2018-11-06 07:45:00 147 1
... ... ...
2022-03-08 11:30:00 179 1
2022-03-08 12:30:00 173 1
2022-03-08 12:45:00 171 1
2022-03-08 13:15:00 165 1
2022-03-15 23:46:00 155 1

10409 rows × 2 columns

2. Dividir el conjunto de datos¶

In [112]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [113]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','Day_of_Week']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','Day_of_Week']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','Day_of_Week']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_12"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_16 (LSTM)              (None, 64)                17152     
                                                                 
 dense_12 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,217
Trainable params: 17,217
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [114]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 8s 5ms/step - loss: 4.2882 - val_loss: 0.6797
Epoch 2/20
1471/1471 [==============================] - 6s 4ms/step - loss: 3.2641 - val_loss: 0.5768
Epoch 3/20
1471/1471 [==============================] - 6s 4ms/step - loss: 3.2291 - val_loss: 3.2235
Epoch 4/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.5128 - val_loss: 1.7053
Epoch 5/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.4980 - val_loss: 0.7168

5. Evaluar¶

In [115]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_day_of_week_2 = train_eval
val_eval_measurement_day_of_week_2 = val_eval
test_eval_measurement_day_of_week_2 = test_eval

train_rmse_measurement_day_of_week_2 = train_rmse
val_rmse_measurement_day_of_week_2 = val_rmse
test_rmse_measurement_day_of_week_2 = test_rmse
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.9719053506851196
Evaluación Validación MAE: 0.7168444991111755
Evaluación Prueba MAE: 0.7220906019210815
Entrenamiento RMSE: 6.047867774963379
Validación RMSE: 1.6844182014465332
Prueba RMSE: 1.7180368900299072

6. Gráfica¶

In [116]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [117]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement','Day_of_Week']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 2))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1]]], axis=0)

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 16ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00        164.85527

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [118]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Day_of_Week']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','Day_of_Week']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','Day_of_Week']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','Day_of_Week']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_day_of_week_1 = train_eval
val_eval_measurement_day_of_week_1 = val_eval
test_eval_measurement_day_of_week_1 = test_eval

train_rmse_measurement_day_of_week_1 = train_rmse
val_rmse_measurement_day_of_week_1 = val_rmse
test_rmse_measurement_day_of_week_1 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_13"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_17 (LSTM)              (None, 32)                4480      
                                                                 
 dense_13 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,513
Trainable params: 4,513
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 7s 3ms/step - loss: 133.0766 - val_loss: 117.6279
Epoch 2/20
1471/1471 [==============================] - 4s 3ms/step - loss: 94.6215 - val_loss: 80.5950
Epoch 3/20
1471/1471 [==============================] - 4s 3ms/step - loss: 58.8337 - val_loss: 45.2678
Epoch 4/20
1471/1471 [==============================] - 4s 3ms/step - loss: 26.9654 - val_loss: 13.8269
Epoch 5/20
1471/1471 [==============================] - 4s 3ms/step - loss: 14.4304 - val_loss: 7.9459
Epoch 6/20
1471/1471 [==============================] - 4s 3ms/step - loss: 8.6724 - val_loss: 3.5847
Epoch 7/20
1471/1471 [==============================] - 4s 3ms/step - loss: 5.3736 - val_loss: 2.1579
Epoch 8/20
1471/1471 [==============================] - 4s 3ms/step - loss: 4.0482 - val_loss: 1.8326
Epoch 9/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.4654 - val_loss: 0.8030
Epoch 10/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.0154 - val_loss: 1.2669
Epoch 11/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.7274 - val_loss: 0.6036
Epoch 12/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.4312 - val_loss: 0.5182
Epoch 13/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.3802 - val_loss: 0.7926
Epoch 14/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2359 - val_loss: 0.7777
Epoch 15/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2415 - val_loss: 0.5503
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 0s 1ms/step
368/368 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 2.041555404663086
Evaluación Validación MAE: 0.5502681136131287
Evaluación Prueba MAE: 0.7457050681114197
Entrenamiento RMSE: 6.436107158660889
Validación RMSE: 1.4146066904067993
Prueba RMSE: 1.8690282106399536

LSTM_3¶

In [119]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Day_of_Week']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement','Day_of_Week']], seq_length)
X_val, y_val = create_sequences(val[['Measurement','Day_of_Week']], seq_length)
X_test, y_test = create_sequences(test[['Measurement','Day_of_Week']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 2))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 2))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 2))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identifica el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_day_of_week_3 = train_eval
val_eval_measurement_day_of_week_3 = val_eval
test_eval_measurement_day_of_week_3 = test_eval

train_rmse_measurement_day_of_week_3 = train_rmse
val_rmse_measurement_day_of_week_3 = val_rmse
test_rmse_measurement_day_of_week_3 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_14"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_18 (LSTM)              (None, 8, 32)             4480      
                                                                 
 batch_normalization_8 (Batc  (None, 8, 32)            128       
 hNormalization)                                                 
                                                                 
 dropout_8 (Dropout)         (None, 8, 32)             0         
                                                                 
 lstm_19 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_9 (Batc  (None, 64)               256       
 hNormalization)                                                 
                                                                 
 dropout_9 (Dropout)         (None, 64)                0         
                                                                 
 dense_14 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,761
Trainable params: 29,569
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 12s 7ms/step - loss: 46.7021 - val_loss: 4.3623
Epoch 2/20
1471/1471 [==============================] - 9s 6ms/step - loss: 22.3739 - val_loss: 9.6406
Epoch 3/20
1471/1471 [==============================] - 9s 6ms/step - loss: 22.5934 - val_loss: 8.9447
Epoch 4/20
1471/1471 [==============================] - 9s 6ms/step - loss: 22.2997 - val_loss: 8.6781
2942/2942 [==============================] - 6s 2ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 16.368377685546875
Evaluación Validación MAE: 8.678139686584473
Evaluación Prueba MAE: 17.76243019104004
Entrenamiento RMSE: 22.417497634887695
Validación RMSE: 11.534538269042969
Prueba RMSE: 19.966222763061523

Algoritmo LSTM con características de entrada: 'Measurement', 'In_Range', 'Hypoglycemia' y 'Hyperglycemia', 'Hour_of_Day' y 'Day_of_Week'.¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Measurement', 'In_Range', 'Hypoglycemia' y 'Hyperglycemia', 'Hour_of_Day' y 'Day_of_Week'.

  • 'Mesurement' es el nivel de glucosa.
  • "In_Range" indica si una medición está dentro del rango de niveles normales.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hour_of_Day" para indicar la hora del día en que se tomó cada medición.
  • "Day_of_Week" para indicar el día de la semana. 0 es lunes y 6 es domingo.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [120]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [121]:
df
Out[121]:
Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-11-06 06:45:00 134 True False False 6 1
2018-11-06 07:00:00 134 True False False 7 1
2018-11-06 07:15:00 139 True False False 7 1
2018-11-06 07:30:00 145 True False False 7 1
2018-11-06 07:45:00 147 True False False 7 1
... ... ... ... ... ... ...
2022-03-08 11:30:00 179 True False False 11 1
2022-03-08 12:30:00 173 True False False 12 1
2022-03-08 12:45:00 171 True False False 12 1
2022-03-08 13:15:00 165 True False False 13 1
2022-03-15 23:46:00 155 True False False 23 1

10409 rows × 6 columns

2. Dividir el conjunto de datos¶

In [122]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [123]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 6))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 6))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 6))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_15"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_20 (LSTM)              (None, 64)                18176     
                                                                 
 dense_15 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 18,241
Trainable params: 18,241
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [124]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 8s 5ms/step - loss: 3.6493 - val_loss: 1.3355
Epoch 2/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.6834 - val_loss: 1.2321
Epoch 3/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.5713 - val_loss: 0.4833
Epoch 4/20
1471/1471 [==============================] - 7s 4ms/step - loss: 2.3591 - val_loss: 0.7042
Epoch 5/20
1471/1471 [==============================] - 6s 4ms/step - loss: 2.0673 - val_loss: 2.0020
Epoch 6/20
1471/1471 [==============================] - 6s 4ms/step - loss: 1.8955 - val_loss: 0.5892

5. Evaluar¶

In [125]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_combined_2 = train_eval
val_eval_measurement_combined_2 = val_eval
test_eval_measurement_combined_2 = test_eval

train_rmse_measurement_combined_2 = train_rmse
val_rmse_measurement_combined_2 = val_rmse
test_rmse_measurement_combined_2 = test_rmse
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.5551469326019287
Evaluación Validación MAE: 0.5891621708869934
Evaluación Prueba MAE: 0.49372732639312744
Entrenamiento RMSE: 4.980219841003418
Validación RMSE: 1.3859299421310425
Prueba RMSE: 1.3617255687713623

6. Gráfica¶

In [126]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [127]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 6))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2], last_sequence[-1][3], last_sequence[-1][4], last_sequence[-1][5]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 33ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00       161.685577

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [128]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 6)) # 6 features
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 6)) # 6 features
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 6)) # 6 features

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_combined_1 = train_eval
val_eval_measurement_combined_1 = val_eval
test_eval_measurement_combined_1 = test_eval

train_rmse_measurement_combined_1 = train_rmse
val_rmse_measurement_combined_1 = val_rmse
test_rmse_measurement_combined_1 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_16"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_21 (LSTM)              (None, 32)                4992      
                                                                 
 dense_16 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 5,025
Trainable params: 5,025
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 7s 3ms/step - loss: 134.2902 - val_loss: 120.5204
Epoch 2/20
1471/1471 [==============================] - 4s 3ms/step - loss: 96.1797 - val_loss: 81.3639
Epoch 3/20
1471/1471 [==============================] - 4s 3ms/step - loss: 59.4585 - val_loss: 45.8203
Epoch 4/20
1471/1471 [==============================] - 4s 3ms/step - loss: 27.7644 - val_loss: 15.1463
Epoch 5/20
1471/1471 [==============================] - 4s 3ms/step - loss: 15.2745 - val_loss: 7.8304
Epoch 6/20
1471/1471 [==============================] - 4s 3ms/step - loss: 11.9656 - val_loss: 7.3648
Epoch 7/20
1471/1471 [==============================] - 4s 3ms/step - loss: 8.2114 - val_loss: 4.2578
Epoch 8/20
1471/1471 [==============================] - 4s 3ms/step - loss: 5.5384 - val_loss: 3.3430
Epoch 9/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.8932 - val_loss: 1.1907
Epoch 10/20
1471/1471 [==============================] - 4s 3ms/step - loss: 3.2064 - val_loss: 1.1827
Epoch 11/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.7160 - val_loss: 1.4347
Epoch 12/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.5795 - val_loss: 0.7833
Epoch 13/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.4383 - val_loss: 0.7639
Epoch 14/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2427 - val_loss: 0.9210
Epoch 15/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2201 - val_loss: 0.4965
Epoch 16/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.1251 - val_loss: 0.9619
Epoch 17/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.0650 - val_loss: 0.7539
Epoch 18/20
1471/1471 [==============================] - 4s 3ms/step - loss: 2.2114 - val_loss: 1.2365
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 0s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 3.5557382106781006
Evaluación Validación MAE: 1.2365148067474365
Evaluación Prueba MAE: 1.395617127418518
Entrenamiento RMSE: 6.954303741455078
Validación RMSE: 1.8586605787277222
Prueba RMSE: 2.1893370151519775

LSTM_3¶

In [129]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'In_Range', 'Hypoglycemia', 'Hyperglycemia', 'Hour_of_Day', 'Day_of_Week']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 6)) # 6 features
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 6)) # 6 features
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 6)) # 6 features

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_combined_3 = train_eval
val_eval_measurement_combined_3 = val_eval
test_eval_measurement_combined_3 = test_eval

train_rmse_measurement_combined_3 = train_rmse
val_rmse_measurement_combined_3 = val_rmse
test_rmse_measurement_combined_3 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_17"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_22 (LSTM)              (None, 8, 32)             4992      
                                                                 
 batch_normalization_10 (Bat  (None, 8, 32)            128       
 chNormalization)                                                
                                                                 
 dropout_10 (Dropout)        (None, 8, 32)             0         
                                                                 
 lstm_23 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_11 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_11 (Dropout)        (None, 64)                0         
                                                                 
 dense_17 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 30,273
Trainable params: 30,081
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1471/1471 [==============================] - 12s 7ms/step - loss: 46.7815 - val_loss: 6.0649
Epoch 2/20
1471/1471 [==============================] - 10s 7ms/step - loss: 22.6286 - val_loss: 2.1474
Epoch 3/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.7043 - val_loss: 6.4587
Epoch 4/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.3712 - val_loss: 10.5936
Epoch 5/20
1471/1471 [==============================] - 10s 7ms/step - loss: 21.1550 - val_loss: 6.9846
2942/2942 [==============================] - 6s 2ms/step
368/368 [==============================] - 1s 2ms/step
368/368 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 7.509009838104248
Evaluación Validación MAE: 6.984602928161621
Evaluación Prueba MAE: 11.392088890075684
Entrenamiento RMSE: 10.64040470123291
Validación RMSE: 7.886087894439697
Prueba RMSE: 12.05888557434082

Resultado obtenido con 3 configuraciones de hiperparámetros LSTM¶

Después de realizar multiples pruebas se puede determinar que configuración de hiperparámetros del modelo LSTMy con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement'
  • 'Measurement' + In_Range
  • 'Measurement' + Hypoglycemia + Hyperglycemia
  • 'Measurement' + Hour_of_Day
  • 'Measurement' + Day_of_Week
  • 'Measurement' + In_Range + Hypoglycemia + Hyperglycemia + Hour_of_Day + Day_of_Week

El hecho de hacer pruebas con cada una de ellas se debe a que se quería inferir si el uso de alguna de las características de entrada ofrecía unos resultados muy distintos, pero después de llevar a cabo cada una de las características de entrada por separado se puede determinar que todas dan resultados similares lo cual hace pensar que juntar todas puede dar mejores resultados.

Podemos ver que según la arquitectura LSTM, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura LSTM y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA LSTM_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [439]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement',
    'Measurement + In_Range',
    'Measurement + Hypo_Hyper',
    'Measurement + Hour_of_day',
    'Measurement + Day_of_week',
    'Todas combinaciones'
])

# Definir los valores en cada posición
tabla.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla.loc[1, 'Measurement'] = train_eval_measurement_1
tabla.loc[2, 'Measurement'] = val_eval_measurement_1
tabla.loc[3, 'Measurement'] = test_eval_measurement_1

tabla.loc[4, 'Measurement'] = train_rmse_measurement_1
tabla.loc[5, 'Measurement'] = val_rmse_measurement_1
tabla.loc[6, 'Measurement'] = test_rmse_measurement_1

tabla.loc[1, 'Measurement + In_Range'] = train_eval_measurement_in_range_1
tabla.loc[2, 'Measurement + In_Range'] = val_eval_measurement_in_range_1
tabla.loc[3, 'Measurement + In_Range'] = test_eval_measurement_in_range_1

tabla.loc[4, 'Measurement + In_Range'] = train_rmse_measurement_in_range_1
tabla.loc[5, 'Measurement + In_Range'] = val_rmse_measurement_in_range_1
tabla.loc[6, 'Measurement + In_Range'] = test_rmse_measurement_in_range_1

tabla.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1
tabla.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1
tabla.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1

tabla.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1
tabla.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1
tabla.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1

tabla.loc[1, 'Measurement + Hour_of_day'] = train_eval_measurement_hour_of_day_1
tabla.loc[2, 'Measurement + Hour_of_day'] = val_eval_measurement_hour_of_day_1
tabla.loc[3, 'Measurement + Hour_of_day'] = test_eval_measurement_hour_of_day_1

tabla.loc[4, 'Measurement + Hour_of_day'] = train_rmse_measurement_hour_of_day_1
tabla.loc[5, 'Measurement + Hour_of_day'] = val_rmse_measurement_hour_of_day_1
tabla.loc[6, 'Measurement + Hour_of_day'] = test_rmse_measurement_hour_of_day_1

tabla.loc[1, 'Measurement + Day_of_week'] = train_eval_measurement_day_of_week_1
tabla.loc[2, 'Measurement + Day_of_week'] = val_eval_measurement_day_of_week_1
tabla.loc[3, 'Measurement + Day_of_week'] = test_eval_measurement_day_of_week_1

tabla.loc[4, 'Measurement + Day_of_week'] = train_rmse_measurement_day_of_week_1
tabla.loc[5, 'Measurement + Day_of_week'] = val_rmse_measurement_day_of_week_1
tabla.loc[6, 'Measurement + Day_of_week'] = test_rmse_measurement_day_of_week_1

tabla.loc[1, 'Todas combinaciones'] = train_eval_measurement_combined_1
tabla.loc[2, 'Todas combinaciones'] = val_eval_measurement_combined_1
tabla.loc[3, 'Todas combinaciones'] = test_eval_measurement_combined_1

tabla.loc[4, 'Todas combinaciones'] = train_rmse_measurement_combined_1
tabla.loc[5, 'Todas combinaciones'] = val_rmse_measurement_combined_1
tabla.loc[6, 'Todas combinaciones'] = test_rmse_measurement_combined_1

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child)', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_1
Evaluación+Métrica Measurement Measurement + In_Range Measurement + Hypo_Hyper Measurement + Hour_of_day Measurement + Day_of_week Todas combinaciones
Entrenamiento MAE 2.531723 1.956263 2.052378 2.790248 2.041555 3.555738
Validación MAE 0.683911 0.615551 0.629569 1.106804 0.550268 1.236515
Test MAE 1.772164 1.181051 1.268514 2.241770 0.745705 1.395617
Entrenamiento RMSE 7.263628 6.228616 6.387573 6.707801 6.436107 6.954304
Validación RMSE 1.659944 1.474732 1.453710 1.923299 1.414607 1.858661
Test RMSE 2.984234 2.073377 2.210016 3.132214 1.869028 2.189337
In [440]:
import pandas as pd
from IPython.display import HTML, display

# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla.copy()
cols_to_convert = ['Measurement', 'Measurement + In_Range', 'Measurement + Hypo_Hyper', 
                   'Measurement + Hour_of_day', 'Measurement + Day_of_week', 'Todas combinaciones']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
new_tabla1 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    new_tabla1 = pd.concat([new_tabla1, pd.DataFrame([row])])

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR LSTM_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = new_tabla1.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()
# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

MEJOR LSTM_1
Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 1.956263 Measurement + In_Range
Validación MAE 0.550268 Measurement + Day_of_week
Test MAE 0.745705 Measurement + Day_of_week
Entrenamiento RMSE 6.228616 Measurement + In_Range
Validación RMSE 1.414607 Measurement + Day_of_week
Test RMSE 1.869028 Measurement + Day_of_week

TABLA LSTM_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [441]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement',
    'Measurement + In_Range',
    'Measurement + Hypo_Hyper',
    'Measurement + Hour_of_day',
    'Measurement + Day_of_week',
    'Todas combinaciones'
])

# Definir los valores en cada posición
tabla.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla.loc[1, 'Measurement'] = train_eval_measurement_2
tabla.loc[2, 'Measurement'] = val_eval_measurement_2
tabla.loc[3, 'Measurement'] = test_eval_measurement_2

tabla.loc[4, 'Measurement'] = train_rmse_measurement_2
tabla.loc[5, 'Measurement'] = val_rmse_measurement_2
tabla.loc[6, 'Measurement'] = test_rmse_measurement_2

tabla.loc[1, 'Measurement + In_Range'] = train_eval_measurement_in_range_2
tabla.loc[2, 'Measurement + In_Range'] = val_eval_measurement_in_range_2
tabla.loc[3, 'Measurement + In_Range'] = test_eval_measurement_in_range_2

tabla.loc[4, 'Measurement + In_Range'] = train_rmse_measurement_in_range_2
tabla.loc[5, 'Measurement + In_Range'] = val_rmse_measurement_in_range_2
tabla.loc[6, 'Measurement + In_Range'] = test_rmse_measurement_in_range_2

tabla.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2
tabla.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2
tabla.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2

tabla.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2
tabla.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2
tabla.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2

tabla.loc[1, 'Measurement + Hour_of_day'] = train_eval_measurement_hour_of_day_2
tabla.loc[2, 'Measurement + Hour_of_day'] = val_eval_measurement_hour_of_day_2
tabla.loc[3, 'Measurement + Hour_of_day'] = test_eval_measurement_hour_of_day_2

tabla.loc[4, 'Measurement + Hour_of_day'] = train_rmse_measurement_hour_of_day_2
tabla.loc[5, 'Measurement + Hour_of_day'] = val_rmse_measurement_hour_of_day_2
tabla.loc[6, 'Measurement + Hour_of_day'] = test_rmse_measurement_hour_of_day_2

tabla.loc[1, 'Measurement + Day_of_week'] = train_eval_measurement_day_of_week_2
tabla.loc[2, 'Measurement + Day_of_week'] = val_eval_measurement_day_of_week_2
tabla.loc[3, 'Measurement + Day_of_week'] = test_eval_measurement_day_of_week_2

tabla.loc[4, 'Measurement + Day_of_week'] = train_rmse_measurement_day_of_week_2
tabla.loc[5, 'Measurement + Day_of_week'] = val_rmse_measurement_day_of_week_2
tabla.loc[6, 'Measurement + Day_of_week'] = test_rmse_measurement_day_of_week_2

tabla.loc[1, 'Todas combinaciones'] = train_eval_measurement_combined_2
tabla.loc[2, 'Todas combinaciones'] = val_eval_measurement_combined_2
tabla.loc[3, 'Todas combinaciones'] = test_eval_measurement_combined_2

tabla.loc[4, 'Todas combinaciones'] = train_rmse_measurement_combined_2
tabla.loc[5, 'Todas combinaciones'] = val_rmse_measurement_combined_2
tabla.loc[6, 'Todas combinaciones'] = test_rmse_measurement_combined_2

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child)', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_2
Evaluación+Métrica Measurement Measurement + In_Range Measurement + Hypo_Hyper Measurement + Hour_of_day Measurement + Day_of_week Todas combinaciones
Entrenamiento MAE 1.820348 1.706365 1.235351 1.842405 1.971905 1.555147
Validación MAE 1.028662 0.970508 0.388647 0.900119 0.716844 0.589162
Test MAE 1.324980 0.918810 0.293932 0.577552 0.722091 0.493727
Entrenamiento RMSE 5.082149 4.773817 4.759490 5.213830 6.047868 4.980220
Validación RMSE 1.588101 1.494988 1.237700 1.511751 1.684418 1.385930
Test RMSE 1.809658 1.491521 1.244411 1.389298 1.718037 1.361726
In [442]:
import pandas as pd
from IPython.display import HTML, display

# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla.copy()
cols_to_convert = ['Measurement', 'Measurement + In_Range', 'Measurement + Hypo_Hyper', 
                   'Measurement + Hour_of_day', 'Measurement + Day_of_week', 'Todas combinaciones']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
new_tabla2 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    new_tabla2 = pd.concat([new_tabla2, pd.DataFrame([row])])

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR LSTM_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = new_tabla2.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

MEJOR LSTM_2
Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper
Validación MAE 0.388647 Measurement + Hypo_Hyper
Test MAE 0.293932 Measurement + Hypo_Hyper
Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper
Validación RMSE 1.237700 Measurement + Hypo_Hyper
Test RMSE 1.244411 Measurement + Hypo_Hyper

TABLA LSTM_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • return_sequences: True.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5.
  • units: 64. Número de neuronas en la capa LSTM.
  • return_sequences: False. True/False. Determina si una capa LSTM devuelve la secuencia completa de salidas o solo la última; 'True' devuelve toda la secuencia, 'False' solo la última salida.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001). Aplica penalizaciones a los coeficientes de la capa durante el entrenamiento, en este caso utilizando la regularización L1 y L2, lo que puede ayudar a prevenir el sobreajuste.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [443]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement',
    'Measurement + In_Range',
    'Measurement + Hypo_Hyper',
    'Measurement + Hour_of_day',
    'Measurement + Day_of_week',
    'Todas combinaciones'
])

# Definir los valores en cada posición
tabla.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla.loc[1, 'Measurement'] = train_eval_measurement_3
tabla.loc[2, 'Measurement'] = val_eval_measurement_3
tabla.loc[3, 'Measurement'] = test_eval_measurement_3

tabla.loc[4, 'Measurement'] = train_rmse_measurement_3
tabla.loc[5, 'Measurement'] = val_rmse_measurement_3
tabla.loc[6, 'Measurement'] = test_rmse_measurement_3

tabla.loc[1, 'Measurement + In_Range'] = train_eval_measurement_in_range_3
tabla.loc[2, 'Measurement + In_Range'] = val_eval_measurement_in_range_3
tabla.loc[3, 'Measurement + In_Range'] = test_eval_measurement_in_range_3

tabla.loc[4, 'Measurement + In_Range'] = train_rmse_measurement_in_range_3
tabla.loc[5, 'Measurement + In_Range'] = val_rmse_measurement_in_range_3
tabla.loc[6, 'Measurement + In_Range'] = test_rmse_measurement_in_range_3

tabla.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3
tabla.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3
tabla.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3

tabla.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3
tabla.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3
tabla.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3

tabla.loc[1, 'Measurement + Hour_of_day'] = train_eval_measurement_hour_of_day_3
tabla.loc[2, 'Measurement + Hour_of_day'] = val_eval_measurement_hour_of_day_3
tabla.loc[3, 'Measurement + Hour_of_day'] = test_eval_measurement_hour_of_day_3

tabla.loc[4, 'Measurement + Hour_of_day'] = train_rmse_measurement_hour_of_day_3
tabla.loc[5, 'Measurement + Hour_of_day'] = val_rmse_measurement_hour_of_day_3
tabla.loc[6, 'Measurement + Hour_of_day'] = test_rmse_measurement_hour_of_day_3

tabla.loc[1, 'Measurement + Day_of_week'] = train_eval_measurement_day_of_week_3
tabla.loc[2, 'Measurement + Day_of_week'] = val_eval_measurement_day_of_week_3
tabla.loc[3, 'Measurement + Day_of_week'] = test_eval_measurement_day_of_week_3

tabla.loc[4, 'Measurement + Day_of_week'] = train_rmse_measurement_day_of_week_3
tabla.loc[5, 'Measurement + Day_of_week'] = val_rmse_measurement_day_of_week_3
tabla.loc[6, 'Measurement + Day_of_week'] = test_rmse_measurement_day_of_week_3

tabla.loc[1, 'Todas combinaciones'] = train_eval_measurement_combined_3
tabla.loc[2, 'Todas combinaciones'] = val_eval_measurement_combined_3
tabla.loc[3, 'Todas combinaciones'] = test_eval_measurement_combined_3

tabla.loc[4, 'Todas combinaciones'] = train_rmse_measurement_combined_3
tabla.loc[5, 'Todas combinaciones'] = val_rmse_measurement_combined_3
tabla.loc[6, 'Todas combinaciones'] = test_rmse_measurement_combined_3

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child)', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_3
Evaluación+Métrica Measurement Measurement + In_Range Measurement + Hypo_Hyper Measurement + Hour_of_day Measurement + Day_of_week Todas combinaciones
Entrenamiento MAE 7.282508 4.057898 9.994306 6.899686 16.368378 7.509010
Validación MAE 4.092206 2.149173 9.917739 6.182288 8.678140 6.984603
Test MAE 6.770112 3.895165 15.304161 10.067994 17.762430 11.392089
Entrenamiento RMSE 10.493366 6.431543 12.998583 9.938611 22.417498 10.640405
Validación RMSE 4.704648 2.756272 11.009345 7.107999 11.534538 7.886088
Test RMSE 6.899119 4.294253 16.233892 10.758073 19.966223 12.058886
In [444]:
import pandas as pd
from IPython.display import HTML, display

# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla.copy()
cols_to_convert = ['Measurement', 'Measurement + In_Range', 'Measurement + Hypo_Hyper', 
                   'Measurement + Hour_of_day', 'Measurement + Day_of_week', 'Todas combinaciones']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
new_tabla3 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    new_tabla3 = pd.concat([new_tabla3, pd.DataFrame([row])])

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = new_tabla3.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")

styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

MEJOR LSTM_3
Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 4.057898 Measurement + In_Range
Validación MAE 2.149173 Measurement + In_Range
Test MAE 3.895165 Measurement + In_Range
Entrenamiento RMSE 6.431543 Measurement + In_Range
Validación RMSE 2.756272 Measurement + In_Range
Test RMSE 4.294253 Measurement + In_Range

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3¶

In [445]:
# Combina las tablas horizontalmente
combined_table = pd.concat([new_tabla1, new_tabla2, new_tabla3], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 1.956263 Measurement + In_Range Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper Entrenamiento MAE 4.057898 Measurement + In_Range
Validación MAE 0.550268 Measurement + Day_of_week Validación MAE 0.388647 Measurement + Hypo_Hyper Validación MAE 2.149173 Measurement + In_Range
Test MAE 0.745705 Measurement + Day_of_week Test MAE 0.293932 Measurement + Hypo_Hyper Test MAE 3.895165 Measurement + In_Range
Entrenamiento RMSE 6.228616 Measurement + In_Range Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper Entrenamiento RMSE 6.431543 Measurement + In_Range
Validación RMSE 1.414607 Measurement + Day_of_week Validación RMSE 1.237700 Measurement + Hypo_Hyper Validación RMSE 2.756272 Measurement + In_Range
Test RMSE 1.869028 Measurement + Day_of_week Test RMSE 1.244411 Measurement + Hypo_Hyper Test RMSE 4.294253 Measurement + In_Range

RESULTADO DE LA COMPARATIVA A MEJOR LSTM_1, LSTM_2 y LSTM_3¶

In [446]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'LSTM'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = new_tabla1.loc[new_tabla1['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not new_tabla1.loc[new_tabla1['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = new_tabla1.loc[new_tabla1['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = new_tabla2.loc[new_tabla2['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not new_tabla2.loc[new_tabla2['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = new_tabla2.loc[new_tabla2['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = new_tabla3.loc[new_tabla3['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not new_tabla3.loc[new_tabla3['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = new_tabla3.loc[new_tabla3['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'LSTM': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO LSTM</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>LSTM_2 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
LSTM_22 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{LSTM_22}</div>'))

MEJOR RESULTADO LSTM

LSTM_2
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper 2
Validación MAE 0.388647 Measurement + Hypo_Hyper 2
Test MAE 0.293932 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper 2
Validación RMSE 1.237700 Measurement + Hypo_Hyper 2
Test RMSE 1.244411 Measurement + Hypo_Hyper 2

Conclusión LSTM¶

La LSTM (Long Short-Term Memory) es un tipo de red neuronal recurrente diseñada para modelar y aprender patrones en secuencias de datos. Se utiliza mucho para tareas relacionadas con secuencias, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

La LSTM no sufre degradación del gradiente ni dificultades en la captura de dependencias a largo plazo debido a que utilizan una estructura interna de neuronas que les permite tener una memoria de información relevante a larga plazo y olvidar información obsoleta.

La LSTM tiene neuronas y cada una de las neuronas tiene tres puertas principales:

  1. Puerta de entrada (input gate): Determina qué nueva información debe agregarse al estado actual.
  2. Puerta de olvido (forget gate): Determina qué información antigua debe descartarse del estado actual.
  3. Puerta de salida (output gate): Determina qué parte del estado actual debe emitirse como salida. Además, las puertas contienen funciones de activación.

El diseño de las LSTMs permite que las unidades LSTM mantengan una memoria a largo plazo de la información relevante y eviten que la información se diluya a medida que se procesa a través de la secuencia. Esto las hace especialmente efectivas para modelar dependencias a largo plazo y capturar patrones complejos en datos secuenciales.

En resumen, una LSTM es una unidad recurrente que utiliza una estructura de neuronas con puertas de entrada, olvido y salida para modelar y aprender patrones en secuencias de datos. Su diseño les permite recordar información relevante a largo plazo y superar los problemas de degradación del gradiente, permitiendo un procesamiento efectivo de secuencias.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo LSTM es entrenado, se puede determinar que la LSTM_2 obtiene mejores resultados e incluso se puede apreciar que las características de entrada con mejores resultados son Measurement + Hypo_Hyper.

En un análisis global LSTM:

  • Mejor LSTM_2:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor LSTM es la 2, utilizando las características de entrada Measurement + Hypo_Hyper.

Este análisis de resultados concluye que los próximos análisis se hagan con las características de entrada Measurement + Hypo_Hyper ya que han obtenido globalmente unos mejores resultados en rendimiento de predicción y precisión al entrenamiento y generalización a nuevos datos.

Redes Neuronales Recurrentes Convolucionales (CRNN)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo CRNN con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.¶

En este caso se esta entrenando al algoritmo CRNN con las variables 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal

Mejor arquitectura 3¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo CRNN:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [463]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [464]:
df
Out[464]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-11-06 06:45:00 134 False False
2018-11-06 07:00:00 134 False False
2018-11-06 07:15:00 139 False False
2018-11-06 07:30:00 145 False False
2018-11-06 07:45:00 147 False False
... ... ... ...
2022-03-08 11:30:00 179 False False
2022-03-08 12:30:00 173 False False
2022-03-08 12:45:00 171 False False
2022-03-08 13:15:00 165 False False
2022-03-15 23:46:00 155 False False

10409 rows × 3 columns

2. Dividir el conjunto de datos¶

In [465]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [466]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.5))
model.add(LSTM(64))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')
Model: "sequential_60"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv1d_16 (Conv1D)          (None, 6, 32)             320       
                                                                 
 batch_normalization_32 (Bat  (None, 6, 32)            128       
 chNormalization)                                                
                                                                 
 max_pooling1d_16 (MaxPoolin  (None, 3, 32)            0         
 g1D)                                                            
                                                                 
 dropout_32 (Dropout)        (None, 3, 32)             0         
                                                                 
 lstm_70 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_33 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_33 (Dropout)        (None, 64)                0         
                                                                 
 dense_60 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 25,601
Trainable params: 25,409
Non-trainable params: 192
_________________________________________________________________

4. Entrenar el modelo¶

In [467]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1471/1471 [==============================] - 9s 5ms/step - loss: 45.7227 - val_loss: 29.6842
Epoch 2/20
1471/1471 [==============================] - 6s 4ms/step - loss: 22.2521 - val_loss: 20.6830
Epoch 3/20
1471/1471 [==============================] - 6s 4ms/step - loss: 21.6611 - val_loss: 17.2883
Epoch 4/20
1471/1471 [==============================] - 6s 4ms/step - loss: 21.3846 - val_loss: 4.2058
Epoch 5/20
1471/1471 [==============================] - 6s 4ms/step - loss: 21.2348 - val_loss: 32.2544
Epoch 6/20
1471/1471 [==============================] - 6s 4ms/step - loss: 21.1563 - val_loss: 15.8373
Epoch 7/20
1471/1471 [==============================] - 6s 4ms/step - loss: 21.0899 - val_loss: 7.1090

5. Evaluar¶

In [468]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_CRNN = train_eval
val_eval_measurement_hypo_hype_3_CRNN = val_eval
test_eval_measurement_hypo_hype_3_CRNN = test_eval

train_rmse_measurement_hypo_hype_3_CRNN = train_rmse
val_rmse_measurement_hypo_hype_3_CRNN = val_rmse
test_rmse_measurement_hypo_hype_3_CRNN = test_rmse
2942/2942 [==============================] - 4s 1ms/step
368/368 [==============================] - 1s 1ms/step
368/368 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 9.861878395080566
Evaluación Validación MAE: 7.108992099761963
Evaluación Prueba MAE: 5.153801918029785
Entrenamiento RMSE: 14.02937126159668
Validación RMSE: 8.081631660461426
Prueba RMSE: 7.009936809539795

6. Gráfica¶

In [469]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [470]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 21ms/step

                     Datos predichos
date                                
2022-03-16 00:01:00       154.489594

Distintas arquitecturas CRNN probadas¶

CRNN_ 2¶

In [146]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(64, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_2_CRNN = train_eval
val_eval_measurement_hypo_hype_2_CRNN = val_eval
test_eval_measurement_hypo_hype_2_CRNN = test_eval

train_rmse_measurement_hypo_hype_2_CRNN = train_rmse
val_rmse_measurement_hypo_hype_2_CRNN = val_rmse
test_rmse_measurement_hypo_hype_2_CRNN = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1471/1471 [==============================] - 6s 3ms/step - loss: 135.5546 - val_loss: 123.2281
Epoch 2/20
1471/1471 [==============================] - 3s 2ms/step - loss: 99.0535 - val_loss: 84.7408
Epoch 3/20
1471/1471 [==============================] - 4s 2ms/step - loss: 61.5440 - val_loss: 47.0290
Epoch 4/20
1471/1471 [==============================] - 3s 2ms/step - loss: 28.2490 - val_loss: 15.0705
Epoch 5/20
1471/1471 [==============================] - 3s 2ms/step - loss: 17.5777 - val_loss: 10.3473
Epoch 6/20
1471/1471 [==============================] - 4s 2ms/step - loss: 19.2717 - val_loss: 12.5487
Epoch 7/20
1471/1471 [==============================] - 4s 2ms/step - loss: 19.0440 - val_loss: 12.7272
Epoch 8/20
1471/1471 [==============================] - 4s 2ms/step - loss: 17.9221 - val_loss: 12.1386
2942/2942 [==============================] - 3s 916us/step
368/368 [==============================] - 0s 897us/step
368/368 [==============================] - 0s 893us/step
Evaluación Entrenamiento MAE: 16.716449737548828
Evaluación Validación MAE: 12.138555526733398
Evaluación Prueba MAE: 22.90216827392578
Entrenamiento RMSE: 23.382892608642578
Validación RMSE: 15.201238632202148
Prueba RMSE: 25.331218719482422

CRNN_1¶

In [147]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_22[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_1_CRNN = train_eval
val_eval_measurement_hypo_hype_1_CRNN = val_eval
test_eval_measurement_hypo_hype_1_CRNN = test_eval

train_rmse_measurement_hypo_hype_1_CRNN = train_rmse
val_rmse_measurement_hypo_hype_1_CRNN = val_rmse
test_rmse_measurement_hypo_hype_1_CRNN = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1471/1471 [==============================] - 5s 2ms/step - loss: 129.8758 - val_loss: 114.9176
Epoch 2/20
1471/1471 [==============================] - 3s 2ms/step - loss: 92.2383 - val_loss: 78.1228
Epoch 3/20
1471/1471 [==============================] - 3s 2ms/step - loss: 55.8774 - val_loss: 41.7154
Epoch 4/20
1471/1471 [==============================] - 3s 2ms/step - loss: 24.7522 - val_loss: 12.5521
Epoch 5/20
1471/1471 [==============================] - 3s 2ms/step - loss: 17.2142 - val_loss: 9.0875
Epoch 6/20
1471/1471 [==============================] - 3s 2ms/step - loss: 16.9014 - val_loss: 9.9848
Epoch 7/20
1471/1471 [==============================] - 3s 2ms/step - loss: 17.4265 - val_loss: 10.8580
Epoch 8/20
1471/1471 [==============================] - 3s 2ms/step - loss: 21.1897 - val_loss: 15.5106
2942/2942 [==============================] - 3s 886us/step
368/368 [==============================] - 0s 908us/step
368/368 [==============================] - 0s 888us/step
Evaluación Entrenamiento MAE: 18.141822814941406
Evaluación Validación MAE: 15.510540008544922
Evaluación Prueba MAE: 27.028221130371094
Entrenamiento RMSE: 25.008127212524414
Validación RMSE: 18.640625
Prueba RMSE: 29.33807373046875

Resultado obtenido con 3 configuraciones de hiperparámetros CRNN¶

Después de realizar 3 pruebas se puede determinar que configuración de hiperparámetros del modelo CRNN y con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura CRNN, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas, siendo en este caso la Measurement + Hypoglycemia + Hyperglycemia.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reúne todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura CRNN y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA CRNN_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [471]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_CRNN vacía
tabla_1_CRNN = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_CRNN.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_CRNN.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_CRNN.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_CRNN.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_CRNN.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_CRNN.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_CRNN.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_CRNN
tabla_1_CRNN.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_CRNN
tabla_1_CRNN.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_CRNN

tabla_1_CRNN.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_CRNN
tabla_1_CRNN.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_CRNN
tabla_1_CRNN.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_CRNN

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_CRNN.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 18.141823
Validación MAE 15.510540
Test MAE 27.028221
Entrenamiento RMSE 25.008127
Validación RMSE 18.640625
Test RMSE 29.338074
In [472]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_CRNN.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_CRNN = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_CRNN = pd.concat([tabla_1_CRNN, pd.DataFrame([row])])

TABLA CRNN_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 64. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [473]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_2_CRNN vacía
tabla_2_CRNN = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_CRNN.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_CRNN.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_CRNN.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_CRNN.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_CRNN.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_CRNN.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_CRNN.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_CRNN
tabla_2_CRNN.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_CRNN
tabla_2_CRNN.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_CRNN

tabla_2_CRNN.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_CRNN
tabla_2_CRNN.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_CRNN
tabla_2_CRNN.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_CRNN

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_CRNN.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 16.716450
Validación MAE 12.138556
Test MAE 22.902168
Entrenamiento RMSE 23.382893
Validación RMSE 15.201239
Test RMSE 25.331219
In [474]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_CRNN.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_CRNN = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_CRNN = pd.concat([tabla_2_CRNN, pd.DataFrame([row])])

TABLA CRNN_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [475]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_CRNN vacía
tabla_3_CRNN = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_CRNN.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_CRNN.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_CRNN.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_CRNN.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_CRNN.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_CRNN.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_CRNN.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_CRNN
tabla_3_CRNN.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_CRNN
tabla_3_CRNN.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_CRNN

tabla_3_CRNN.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_CRNN
tabla_3_CRNN.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_CRNN
tabla_3_CRNN.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_CRNN

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_CRNN.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 9.861878
Validación MAE 7.108992
Test MAE 5.153802
Entrenamiento RMSE 14.029371
Validación RMSE 8.081632
Test RMSE 7.009937
In [476]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_CRNN.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_CRNN = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_CRNN = pd.concat([tabla_3_CRNN, pd.DataFrame([row])])

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3¶

In [477]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_CRNN, tabla_2_CRNN, tabla_3_CRNN], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 18.141823 Measurement + Hypo_Hyper Entrenamiento MAE 16.716450 Measurement + Hypo_Hyper Entrenamiento MAE 9.861878 Measurement + Hypo_Hyper
Validación MAE 15.510540 Measurement + Hypo_Hyper Validación MAE 12.138556 Measurement + Hypo_Hyper Validación MAE 7.108992 Measurement + Hypo_Hyper
Test MAE 27.028221 Measurement + Hypo_Hyper Test MAE 22.902168 Measurement + Hypo_Hyper Test MAE 5.153802 Measurement + Hypo_Hyper
Entrenamiento RMSE 25.008127 Measurement + Hypo_Hyper Entrenamiento RMSE 23.382893 Measurement + Hypo_Hyper Entrenamiento RMSE 14.029371 Measurement + Hypo_Hyper
Validación RMSE 18.640625 Measurement + Hypo_Hyper Validación RMSE 15.201239 Measurement + Hypo_Hyper Validación RMSE 8.081632 Measurement + Hypo_Hyper
Test RMSE 29.338074 Measurement + Hypo_Hyper Test RMSE 25.331219 Measurement + Hypo_Hyper Test RMSE 7.009937 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR CRNN_1, CRNN_2 y CRNN_3¶

In [478]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'CRNN'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_CRNN.loc[tabla_1_CRNN['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_CRNN.loc[tabla_1_CRNN['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_CRNN.loc[tabla_1_CRNN['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_CRNN.loc[tabla_2_CRNN['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_CRNN.loc[tabla_2_CRNN['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_CRNN.loc[tabla_2_CRNN['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_CRNN.loc[tabla_3_CRNN['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_CRNN.loc[tabla_3_CRNN['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_CRNN.loc[tabla_3_CRNN['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'CRNN': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO CRNN</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>CRNN_3 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
CRNN_22 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{CRNN_22}</div>'))

MEJOR RESULTADO CRNN

CRNN_3
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 9.861878 Measurement + Hypo_Hyper 3
Validación MAE 7.108992 Measurement + Hypo_Hyper 3
Test MAE 5.153802 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 14.029371 Measurement + Hypo_Hyper 3
Validación RMSE 8.081632 Measurement + Hypo_Hyper 3
Test RMSE 7.009937 Measurement + Hypo_Hyper 3

Conclusión CRNN¶

La Red Neuronal Recurrente Convolucional (CRNN) es una arquitectura de red neuronal que combina características de las redes neuronales convolucionales (CNN) y las redes neuronales recurrentes (RNN). Esta diseñada para modelar y aprender patrones en datos secuenciales, al tiempo que tiene en cuenta la estructura espacial de los datos, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

Al igual que las CNN, las CRNN utilizan capas convolucionales para extraer características locales de los datos de entrada. Estas capas convolucionales aplican filtros a ventanas de tamaño fijo, que se deslizan a lo largo de la secuencia para detectar patrones locales. Esto permite a la CRNN capturar características relevantes en diferentes partes de la secuencia de datos.

Sin embargo, a diferencia de las CNN tradicionales, las CRNN también incorporan la capacidad de modelar dependencias a largo plazo utilizando unidades recurrentes, como las LSTM. Las LSTM en la CRNN actúan como una capa de procesamiento secuencial adicional que se aplica después de las capas convolucionales. Estas unidades recurrentes permiten que la red mantenga una memoria de largo plazo y capture dependencias a largo plazo en la secuencia.

La combinación de capas convolucionales y unidades LSTM en una CRNN aprovecha tanto la capacidad de las CNN para extraer características locales como la capacidad de las RNN para modelar dependencias a largo plazo. Las capas convolucionales ayudan a capturar patrones locales en la secuencia, mientras que las unidades LSTM permiten que la red aprenda y recuerde dependencias a largo plazo entre los elementos de la secuencia.

En resumen, una CRNN es una arquitectura de red neuronal que combina capas convolucionales y unidades LSTM para modelar y aprender patrones en datos secuenciales. Las capas convolucionales extraen características locales de la secuencia, mientras que las unidades LSTM capturan dependencias a largo plazo. Esto permite a la CRNN capturar patrones complejos y modelar tanto la estructura espacial como las dependencias temporales en los datos secuenciales.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo CRNN es entrenado, se puede determinar que la CRNN_3 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global CRNN:

  • Mejor CRNN_3:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor CRNN es la 3 con las características de entrada Measurement + Hypo_Hyper.

Comparativa de resultados LSTM y CRNN¶

In [479]:
from IPython.display import display, HTML

html_tables = f"""
<style>
    .main-container {{
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
    }}
    .table-wrapper {{
        display: inline-block;
        font-size: 14px;
        vertical-align: top;
        margin-right: 20px;
    }}
    .table-wrapper table {{
        font-size: 14px;
    }}
    .table-wrapper th, .table-wrapper td {{
        width: 100px;
        text-align: center;
    }}
    .table-wrapper h3 {{
        text-align: center;
        font-size: 18px;
        color: blue;
    }}
    h2 {{
        text-align: center;
    }}
</style>
<h2>Paciente 22</h2>
<div class="main-container">
    <div class="table-wrapper">
        <h3>LSTM</h3>
        {LSTM_22}
    </div>
    <div class="table-wrapper">
        <h3>CRNN</h3>
        {CRNN_22}
    </div>
</div>
"""


display(HTML(html_tables))

Paciente 22

LSTM

Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper 2
Validación MAE 0.388647 Measurement + Hypo_Hyper 2
Test MAE 0.293932 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper 2
Validación RMSE 1.237700 Measurement + Hypo_Hyper 2
Test RMSE 1.244411 Measurement + Hypo_Hyper 2

CRNN

Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 9.861878 Measurement + Hypo_Hyper 3
Validación MAE 7.108992 Measurement + Hypo_Hyper 3
Test MAE 5.153802 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 14.029371 Measurement + Hypo_Hyper 3
Validación RMSE 8.081632 Measurement + Hypo_Hyper 3
Test RMSE 7.009937 Measurement + Hypo_Hyper 3

Mejor Modelo (Paciente 22)¶

In [480]:
from IPython.display import display, HTML

# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'Arquitectura', 'Configuración Hiperparámetros'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Diccionarios para almacenar los nombres de las tablas
tables_arch1 = {'tabla_1_CRNN': 1, 'tabla_2_CRNN': 2, 'tabla_3_CRNN': 3}
tables_arch2 = {'new_tabla1': 1, 'new_tabla2': 2, 'new_tabla3': 3}

# Para cada fila
for row in rows:
    min_vals = []
    
    # Arquitectura 1
    for table_name, arch in tables_arch1.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'CRNN', arch))

    # Arquitectura 2
    for table_name, arch in tables_arch2.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'LSTM', arch))

    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val, min_col, min_arch, min_num = min(min_vals, key=lambda x: x[0])

    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')

    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'Arquitectura': [min_arch],
        'Configuración Hiperparámetros': [min_num]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px; font-weight:bold;'>MEJOR ARQUITECTURA (Paciente 22)</span></center>"
display(HTML(html_text))

# Aplica el estilo personalizado al DataFrame resultante
styled_result = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_result = styled_result.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Mostrar el DataFrame resultante con estilo y sin índices
final_output = styled_result.hide(axis='index').to_html()

# Utilizando la función `display(HTML())` para mostrar el contenido HTML en un div centrado en la página.
display(HTML("<div style='margin: 0 auto; width:70%'>" + final_output + "</div>"))

# Nombrando
styled_result1 = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])
styled_result1 = styled_result1.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Muestra la predicción del nivel de glucosa en los próximos 15 minutos
# Crea un HTML con el título y los datos

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_table = future_predictions_hypo_hype_2_LSTM_22.style.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

html = """
<div style='text-align: center;'>
<h2>Predicción del nivel de glucosa en los próximos 15 minutos</h2>
<div style='margin: 20px auto 0 40%; width:40%;'>
""" + styled_table.to_html() + "</div></div>"

# Muestra el HTML
display(HTML(html))

# Centrar la figura y la tabla utilizando CSS y HTML
display(HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
<h2>Precisión del modelo con Clarke Error Grid</h2>
"""))


# Mostrar la figura
display(fig_hypo_hype_2_LSTM_22)

# Centrar el contenido de la tabla utilizando CSS y ocultar los índices
styled_table = zone_df_hypo_hype_2_LSTM_22.style.set_properties(**{'text-align': 'center'}).hide(axis='index')

# Convertir la tabla en un objeto HTML
table_html = f"<center>{styled_table.to_html()}</center>"

# Mostrar la tabla centrada
display(HTML(table_html))

MEJOR ARQUITECTURA (Paciente 22)
Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.388647 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.293932 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.237700 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.244411 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-16 00:01:00 157.521851

Precisión del modelo con Clarke Error Grid

Zona Conteo Proporción
A 11758 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Conclusión Paciente 22¶

Los resultados nos aclara que la mejor opción para el paciente 22 es utilizar una arquitectura LSTM con una configuración de hiperparámetros 2.

Arquitectura LSTM con la LSTM_2 global:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

La conclusión final para el paciente 22 es utilizar la arquitectura LSTM con la configuración de hiperparámetros y las características de entrada Measurement + Hour_of_day para la predicción de sus niveles de glucosa para los próximos 15 minutos ya que nos arrojan unos resultados muy buenos.

Paciente 24
¶

La comparativa de soluciones con los modelos RNN y CRNN se va a realizar para el paciente 24 debido a que tras un análisis extenso se ha comprobado que es el 1º con mayor número de mediciones.

Se va a llevar a cabo la comparativa de soluciones con los modelos RNN y CRNN pero con la mejor solución ya obtenida con el paciente 22. De esta manera se va a trabajar con la mejor arquitectura de hiperparámetros y de características de entrada así se va a poder confirmar que el experimento se ha realizado con éxito.

La característica de entrada va a ser: 'Measurement + Hypo_Hyper'

In [159]:
# Filtrar los datos para el paciente 24
df_paciente_24 = df_top_5_pacientes[df_top_5_pacientes['Patient_ID'] == 24]

# Imprimir los resultados
df_paciente_24
Out[159]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-07-13 10:15:00 24 2018-07-13 1900-01-01 10:15:00 142 False True False 10 4
2018-07-13 10:30:00 24 2018-07-13 1900-01-01 10:30:00 142 True False False 10 4
2018-07-13 10:45:00 24 2018-07-13 1900-01-01 10:45:00 153 True False False 10 4
2018-07-13 11:00:00 24 2018-07-13 1900-01-01 11:00:00 160 True False False 11 4
2018-07-13 11:15:00 24 2018-07-13 1900-01-01 11:15:00 162 False False True 11 4
... ... ... ... ... ... ... ... ... ...
2022-03-12 05:30:00 24 2022-03-12 1900-01-01 05:30:00 156 True False False 5 5
2022-03-12 06:30:00 24 2022-03-12 1900-01-01 06:30:00 154 True False False 6 5
2022-03-12 06:45:00 24 2022-03-12 1900-01-01 06:45:00 154 True False False 6 5
2022-03-12 07:45:00 24 2022-03-12 1900-01-01 07:45:00 153 True False False 7 5
2022-03-12 08:30:00 24 2022-03-12 1900-01-01 08:30:00 158 False True False 8 5

11987 rows × 9 columns

Red Neuronal Recurrente (RNN) con el algoritmo Long Short-Term Memory (LSTM)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo LSTM con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Mesurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hyperglycemia" indica si una medición está por encima del rango normal.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [160]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_24[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [161]:
df
Out[161]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-07-13 10:15:00 142 True False
2018-07-13 10:30:00 142 False False
2018-07-13 10:45:00 153 False False
2018-07-13 11:00:00 160 False False
2018-07-13 11:15:00 162 False True
... ... ... ...
2022-03-12 05:30:00 156 False False
2022-03-12 06:30:00 154 False False
2022-03-12 06:45:00 154 False False
2022-03-12 07:45:00 153 False False
2022-03-12 08:30:00 158 True False

11987 rows × 3 columns

2. Dividir el conjunto de datos¶

In [162]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [163]:
# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_21"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_27 (LSTM)              (None, 64)                17408     
                                                                 
 dense_21 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,473
Trainable params: 17,473
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [164]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1606/1606 [==============================] - 9s 5ms/step - loss: 3.9900 - val_loss: 0.8969
Epoch 2/20
1606/1606 [==============================] - 7s 5ms/step - loss: 2.3352 - val_loss: 0.8327
Epoch 3/20
1606/1606 [==============================] - 7s 5ms/step - loss: 2.0061 - val_loss: 1.1397
Epoch 4/20
1606/1606 [==============================] - 7s 5ms/step - loss: 1.9377 - val_loss: 1.5952
Epoch 5/20
1606/1606 [==============================] - 7s 5ms/step - loss: 1.8115 - val_loss: 0.2471
Epoch 6/20
1606/1606 [==============================] - 7s 5ms/step - loss: 1.9890 - val_loss: 0.9598
Epoch 7/20
1606/1606 [==============================] - 7s 5ms/step - loss: 1.9558 - val_loss: 0.4183
Epoch 8/20
1606/1606 [==============================] - 7s 5ms/step - loss: 1.7683 - val_loss: 0.4900

5. Evaluar¶

In [165]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_2_LSTM_24 = train_eval
val_eval_measurement_hypo_hype_2_LSTM_24 = val_eval
test_eval_measurement_hypo_hype_2_LSTM_24 = test_eval

train_rmse_measurement_hypo_hype_2_LSTM_24 = train_rmse
val_rmse_measurement_hypo_hype_2_LSTM_24 = val_rmse
test_rmse_measurement_hypo_hype_2_LSTM_24 = test_rmse
3211/3211 [==============================] - 4s 1ms/step
402/402 [==============================] - 1s 1ms/step
402/402 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.3485816717147827
Evaluación Validación MAE: 0.4899934232234955
Evaluación Prueba MAE: 0.5209770798683167
Entrenamiento RMSE: 4.971604824066162
Validación RMSE: 0.7338888645172119
Prueba RMSE: 0.7729625701904297

6. Gráfica¶

In [166]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [167]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')

# Almacenar dato
future_predictions_hypo_hype_2_LSTM_24 = future_predictions

plt.show()
1/1 [==============================] - 0s 15ms/step

                     Datos predichos
date                                
2022-03-12 08:45:00       158.111694

6.2 Clarke Error Grid¶

In [168]:
def clarke_error_grid(ref_values, pred_values):
    assert (len(ref_values) == len(pred_values)), "Unequal number of values (reference: {}) (prediction: {}).".format(len(ref_values), len(pred_values))

    if max(ref_values) > 400 or max(pred_values) > 400:
        print("Input Warning: the maximum reference value {} or the maximum prediction value {} exceeds the normal physiological range of glucose (<400 mg/dl).".format(max(ref_values), max(pred_values)))
    if min(ref_values) < 0 or min(pred_values) < 0:
        print("Input Warning: the minimum reference value {} or the minimum prediction value {} is less than 0 mg/dl.".format(min(ref_values),  min(pred_values)))

    plt.clf()
    plt.scatter(ref_values, pred_values, marker='o', color='black', s=8)
    plt.title("Clarke Error Grid")
    plt.xlabel("Reference Concentration (mg/dl)")
    plt.ylabel("Prediction Concentration (mg/dl)")
    plt.xticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.yticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.gca().set_facecolor('white')
    plt.gca().set_xlim([0, 400])
    plt.gca().set_ylim([0, 400])
    plt.gca().set_aspect((400)/(400))
    plt.plot([0, 400], [0, 400], ':', c='black')
    plt.plot([0, 175/3], [70, 70], '-', c='black')
    plt.plot([175/3, 400/1.2], [70, 400], '-', c='black')
    plt.plot([70, 70], [84, 400],'-', c='black')
    plt.plot([0, 70], [180, 180], '-', c='black')
    plt.plot([70, 290], [180, 400],'-', c='black')
    plt.plot([70, 70], [0, 56], '-', c='black')
    plt.plot([70, 400], [56, 320],'-', c='black')
    plt.plot([180, 180], [0, 70], '-', c='black')
    plt.plot([180, 400], [70, 70], '-', c='black')
    plt.plot([240, 240], [70, 180],'-', c='black')
    plt.plot([240, 400], [180, 180], '-', c='black')
    plt.plot([130, 180], [0, 70], '-', c='black')
    plt.text(30, 15, "A", fontsize=15)
    plt.text(370, 260, "B", fontsize=15)
    plt.text(280, 370, "B", fontsize=15)
    plt.text(160, 370, "C", fontsize=15)
    plt.text(160, 15, "C", fontsize=15)
    plt.text(30, 140, "D", fontsize=15)
    plt.text(370, 120, "D", fontsize=15)
    plt.text(30, 370, "E", fontsize=15)
    plt.text(370, 15, "E", fontsize=15)

    zone = [0] * 5
    for i in range(len(ref_values)):
        if (ref_values[i] <= 70 and pred_values[i] <= 70) or (pred_values[i] <= 1.2 * ref_values[i] and pred_values[i] >= 0.8 * ref_values[i]):
            zone[0] += 1  # Zone A
        elif (ref_values[i] >= 180 and pred_values[i] <= 70) or (ref_values[i] <= 70 and pred_values[i] >= 180):
            zone[4] += 1  # Zone E
        elif ((ref_values[i] >= 70 and ref_values[i] <= 290) and pred_values[i] >= ref_values[i] + 110) or ((ref_values[i] >= 130 and ref_values[i] <= 180) and (pred_values[i] <= (7/5) * ref_values[i] - 182)):
            zone[2] += 1  # Zone C
        elif (ref_values[i] >= 240 and (pred_values[i] >= 70 and pred_values[i] <= 180)) or (ref_values[i] <= 175/3 and pred_values[i] <= 180 and pred_values[i] >= 70) or ((ref_values[i] >= 175/3 and ref_values[i] <= 70) and pred_values[i] >= (6/5) * ref_values[i]):
            zone[3] += 1  # Zone D
        else:
            zone[1] += 1  # Zone B

    return plt, zone

ref_values = y_test
pred_values = y_pred_test

plt, zone = clarke_error_grid(ref_values, pred_values)

# Asignar la figura a una variable antes de llamar a plt.show()
fig_hypo_hype_2_LSTM_24 = plt.gcf()

# Mostrar la figura
plt.show()

# Crear DataFrame para visualizar el conteo de zonas
zone_df = pd.DataFrame({'Zona': ['A', 'B', 'C', 'D', 'E'], 'Conteo': zone})

# Calcular la proporción de cada zona respecto al total
total = sum(zone)
zone_df['Proporción'] = zone_df['Conteo'] / total

# Mostrar la proporción en porcentaje
zone_df['Proporción'] = zone_df['Proporción'].apply(lambda x: '{:.2f}%'.format(x * 100))

# Asignar el DataFrame a una variable
zone_df_hypo_hype_2_LSTM_24 = zone_df

# Ocultar el índice y mostrar el DataFrame
print("Conteo de Zonas:")
display(zone_df_hypo_hype_2_LSTM_24.style.hide(axis="index"))
Conteo de Zonas:
Zona Conteo Proporción
A 12836 100.00%
B 0 0.00%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [169]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_24[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_1_LSTM_24 = train_eval
val_eval_measurement_hypo_hype_1_LSTM_24 = val_eval
test_eval_measurement_hypo_hype_1_LSTM_24 = test_eval

train_rmse_measurement_hypo_hype_1_LSTM_24 = train_rmse
val_rmse_measurement_hypo_hype_1_LSTM_24 = val_rmse
test_rmse_measurement_hypo_hype_1_LSTM_24 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_22"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_28 (LSTM)              (None, 32)                4608      
                                                                 
 dense_22 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,641
Trainable params: 4,641
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1606/1606 [==============================] - 6s 3ms/step - loss: 133.7667 - val_loss: 120.0609
Epoch 2/20
1606/1606 [==============================] - 5s 3ms/step - loss: 98.9948 - val_loss: 83.5044
Epoch 3/20
1606/1606 [==============================] - 5s 3ms/step - loss: 63.8737 - val_loss: 49.5126
Epoch 4/20
1606/1606 [==============================] - 5s 3ms/step - loss: 33.2281 - val_loss: 18.2794
Epoch 5/20
1606/1606 [==============================] - 5s 3ms/step - loss: 16.2156 - val_loss: 10.6667
Epoch 6/20
1606/1606 [==============================] - 5s 3ms/step - loss: 9.9065 - val_loss: 6.0068
Epoch 7/20
1606/1606 [==============================] - 5s 3ms/step - loss: 6.7497 - val_loss: 2.5281
Epoch 8/20
1606/1606 [==============================] - 5s 3ms/step - loss: 5.5627 - val_loss: 4.8014
Epoch 9/20
1606/1606 [==============================] - 5s 3ms/step - loss: 4.5124 - val_loss: 0.5452
Epoch 10/20
1606/1606 [==============================] - 5s 3ms/step - loss: 3.7966 - val_loss: 0.8754
Epoch 11/20
1606/1606 [==============================] - 5s 3ms/step - loss: 3.3884 - val_loss: 1.7723
Epoch 12/20
1606/1606 [==============================] - 5s 3ms/step - loss: 3.0562 - val_loss: 0.2865
Epoch 13/20
1606/1606 [==============================] - 5s 3ms/step - loss: 2.9106 - val_loss: 1.9457
Epoch 14/20
1606/1606 [==============================] - 5s 3ms/step - loss: 2.6646 - val_loss: 0.1368
Epoch 15/20
1606/1606 [==============================] - 5s 3ms/step - loss: 2.4793 - val_loss: 1.0552
Epoch 16/20
1606/1606 [==============================] - 5s 3ms/step - loss: 2.4677 - val_loss: 0.7554
Epoch 17/20
1606/1606 [==============================] - 5s 3ms/step - loss: 2.4788 - val_loss: 1.3840
3211/3211 [==============================] - 4s 1ms/step
402/402 [==============================] - 0s 1ms/step
402/402 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 2.703345775604248
Evaluación Validación MAE: 1.383949875831604
Evaluación Prueba MAE: 1.3206621408462524
Entrenamiento RMSE: 7.408207893371582
Validación RMSE: 1.4959874153137207
Prueba RMSE: 1.46185302734375

LSTM_3¶

In [170]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_24[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Crear secuencias de entrenamiento y prueba para el modelo LSTM
def create_sequences(data, seq_length):
    xs = []
    ys = []
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
# RMSE Interpreta las mismas unidades que la variable objetivo (nivel de glucosa). Identificas el error de unidades de Glucosa.
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_LSTM_24 = train_eval
val_eval_measurement_hypo_hype_3_LSTM_24 = val_eval
test_eval_measurement_hypo_hype_3_LSTM_24 = test_eval

train_rmse_measurement_hypo_hype_3_LSTM_24 = train_rmse
val_rmse_measurement_hypo_hype_3_LSTM_24 = val_rmse
test_rmse_measurement_hypo_hype_3_LSTM_24 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_23"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_29 (LSTM)              (None, 8, 32)             4608      
                                                                 
 batch_normalization_14 (Bat  (None, 8, 32)            128       
 chNormalization)                                                
                                                                 
 dropout_14 (Dropout)        (None, 8, 32)             0         
                                                                 
 lstm_30 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_15 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_15 (Dropout)        (None, 64)                0         
                                                                 
 dense_23 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,889
Trainable params: 29,697
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1606/1606 [==============================] - 14s 7ms/step - loss: 44.6760 - val_loss: 7.6085
Epoch 2/20
1606/1606 [==============================] - 11s 7ms/step - loss: 24.5012 - val_loss: 9.8793
Epoch 3/20
1606/1606 [==============================] - 11s 7ms/step - loss: 24.0349 - val_loss: 10.9801
Epoch 4/20
1606/1606 [==============================] - 11s 7ms/step - loss: 23.7540 - val_loss: 11.7821
3211/3211 [==============================] - 7s 2ms/step
402/402 [==============================] - 1s 2ms/step
402/402 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 13.224138259887695
Evaluación Validación MAE: 11.782096862792969
Evaluación Prueba MAE: 15.299034118652344
Entrenamiento RMSE: 17.32557487487793
Validación RMSE: 13.610280990600586
Prueba RMSE: 15.48101806640625

Resultado obtenido con 3 configuraciones de hiperparámetros LSTM¶

Después de realizar las pruebas se puede determinar que configuración de hiperparámetros del modelo LSTMy con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura LSTM, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura LSTM y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA LSTM_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [171]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_LSTM_24 vacía
tabla_1_LSTM_24 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_LSTM_24.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_LSTM_24.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_LSTM_24.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_LSTM_24.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_LSTM_24.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_LSTM_24.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_LSTM_24.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_LSTM_24
tabla_1_LSTM_24.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_LSTM_24
tabla_1_LSTM_24.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_LSTM_24

tabla_1_LSTM_24.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_LSTM_24
tabla_1_LSTM_24.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_LSTM_24
tabla_1_LSTM_24.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_LSTM_24

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_LSTM_24.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 2.703346
Validación MAE 1.383950
Test MAE 1.320662
Entrenamiento RMSE 7.408208
Validación RMSE 1.495987
Test RMSE 1.461853
In [172]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_LSTM_24.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_LSTM_24 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_LSTM_24 = pd.concat([tabla_1_LSTM_24, pd.DataFrame([row])])

TABLA LSTM_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [173]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla_2_LSTM_24 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_LSTM_24.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_LSTM_24.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_LSTM_24.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_LSTM_24.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_LSTM_24.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_LSTM_24.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_LSTM_24.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_LSTM_24
tabla_2_LSTM_24.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_LSTM_24
tabla_2_LSTM_24.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_LSTM_24

tabla_2_LSTM_24.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_LSTM_24
tabla_2_LSTM_24.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_LSTM_24
tabla_2_LSTM_24.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_LSTM_24

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_LSTM_24.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 1.348582
Validación MAE 0.489993
Test MAE 0.520977
Entrenamiento RMSE 4.971605
Validación RMSE 0.733889
Test RMSE 0.772963
In [174]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_LSTM_24.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_LSTM_24 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_LSTM_24 = pd.concat([tabla_2_LSTM_24, pd.DataFrame([row])])

TABLA LSTM_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • return_sequences: True.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5.
  • units: 64. Número de neuronas en la capa LSTM.
  • return_sequences: False. True/False. Determina si una capa LSTM devuelve la secuencia completa de salidas o solo la última; 'True' devuelve toda la secuencia, 'False' solo la última salida.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001). Aplica penalizaciones a los coeficientes de la capa durante el entrenamiento, en este caso utilizando la regularización L1 y L2, lo que puede ayudar a prevenir el sobreajuste.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [175]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_LSTM_11 vacía
tabla_3_LSTM_24 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_LSTM_24.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_LSTM_24.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_LSTM_24.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_LSTM_24.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_LSTM_24.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_LSTM_24.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_LSTM_24.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_LSTM_24
tabla_3_LSTM_24.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_LSTM_24
tabla_3_LSTM_24.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_LSTM_24

tabla_3_LSTM_24.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_LSTM_24
tabla_3_LSTM_24.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_LSTM_24
tabla_3_LSTM_24.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_LSTM_24

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_LSTM_24.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 13.224138
Validación MAE 11.782097
Test MAE 15.299034
Entrenamiento RMSE 17.325575
Validación RMSE 13.610281
Test RMSE 15.481018
In [176]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_LSTM_24.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_LSTM_24 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_LSTM_24 = pd.concat([tabla_3_LSTM_24, pd.DataFrame([row])])

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3¶

In [177]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_LSTM_24, tabla_2_LSTM_24, tabla_3_LSTM_24], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 2.703346 Measurement + Hypo_Hyper Entrenamiento MAE 1.348582 Measurement + Hypo_Hyper Entrenamiento MAE 13.224138 Measurement + Hypo_Hyper
Validación MAE 1.383950 Measurement + Hypo_Hyper Validación MAE 0.489993 Measurement + Hypo_Hyper Validación MAE 11.782097 Measurement + Hypo_Hyper
Test MAE 1.320662 Measurement + Hypo_Hyper Test MAE 0.520977 Measurement + Hypo_Hyper Test MAE 15.299034 Measurement + Hypo_Hyper
Entrenamiento RMSE 7.408208 Measurement + Hypo_Hyper Entrenamiento RMSE 4.971605 Measurement + Hypo_Hyper Entrenamiento RMSE 17.325575 Measurement + Hypo_Hyper
Validación RMSE 1.495987 Measurement + Hypo_Hyper Validación RMSE 0.733889 Measurement + Hypo_Hyper Validación RMSE 13.610281 Measurement + Hypo_Hyper
Test RMSE 1.461853 Measurement + Hypo_Hyper Test RMSE 0.772963 Measurement + Hypo_Hyper Test RMSE 15.481018 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR LSTM_1, LSTM_2 y LSTM_3¶

In [178]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'LSTM'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_LSTM_24.loc[tabla_1_LSTM_24['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_LSTM_24.loc[tabla_1_LSTM_24['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_LSTM_24.loc[tabla_1_LSTM_24['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_LSTM_24.loc[tabla_2_LSTM_24['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_LSTM_24.loc[tabla_2_LSTM_24['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_LSTM_24.loc[tabla_2_LSTM_24['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_LSTM_24.loc[tabla_3_LSTM_24['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_LSTM_24.loc[tabla_3_LSTM_24['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_LSTM_24.loc[tabla_3_LSTM_24['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'LSTM': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO LSTM</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>LSTM_2 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
LSTM_24 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{LSTM_24}</div>'))

MEJOR RESULTADO LSTM

LSTM_2
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.348582 Measurement + Hypo_Hyper 2
Validación MAE 0.489993 Measurement + Hypo_Hyper 2
Test MAE 0.520977 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 4.971605 Measurement + Hypo_Hyper 2
Validación RMSE 0.733889 Measurement + Hypo_Hyper 2
Test RMSE 0.772963 Measurement + Hypo_Hyper 2

Conclusión LSTM¶

La LSTM (Long Short-Term Memory) es un tipo de red neuronal recurrente diseñada para modelar y aprender patrones en secuencias de datos. Se utiliza mucho para tareas relacionadas con secuencias, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

La LSTM no sufre degradación del gradiente ni dificultades en la captura de dependencias a largo plazo debido a que utilizan una estructura interna de neuronas que les permite tener una memoria de información relevante a larga plazo y olvidar información obsoleta.

La LSTM tiene neuronas y cada una de las neuronas tiene tres puertas principales:

  1. Puerta de entrada (input gate): Determina qué nueva información debe agregarse al estado actual.
  2. Puerta de olvido (forget gate): Determina qué información antigua debe descartarse del estado actual.
  3. Puerta de salida (output gate): Determina qué parte del estado actual debe emitirse como salida. Además, las puertas contienen funciones de activación.

El diseño de las LSTMs permite que las unidades LSTM mantengan una memoria a largo plazo de la información relevante y eviten que la información se diluya a medida que se procesa a través de la secuencia. Esto las hace especialmente efectivas para modelar dependencias a largo plazo y capturar patrones complejos en datos secuenciales.

En resumen, una LSTM es una unidad recurrente que utiliza una estructura de neuronas con puertas de entrada, olvido y salida para modelar y aprender patrones en secuencias de datos. Su diseño les permite recordar información relevante a largo plazo y superar los problemas de degradación del gradiente, permitiendo un procesamiento efectivo de secuencias.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo LSTM es entrenado, se puede determinar que la LSTM_2 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global LSTM:

  • Mejor LSTM_2:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • MSE Validación: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjutno de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Evaluación Entrenamiento, Evaluación Validación, Evaluación Test, MSE Entrenamiento, MSE Validación, MSE Test estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor LSTM es la 2 con las características de entrada Measurement + Hypo_Hyper.

Redes Neuronales Recurrentes Convolucionales (CRNN)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo CRNN con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.¶

En este caso se esta entrenando al algoritmo CRNN con las variables 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal

Mejor arquitectura 3¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo CRNN:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [497]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_24[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [498]:
df
Out[498]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-07-13 10:15:00 142 True False
2018-07-13 10:30:00 142 False False
2018-07-13 10:45:00 153 False False
2018-07-13 11:00:00 160 False False
2018-07-13 11:15:00 162 False True
... ... ... ...
2022-03-12 05:30:00 156 False False
2022-03-12 06:30:00 154 False False
2022-03-12 06:45:00 154 False False
2022-03-12 07:45:00 153 False False
2022-03-12 08:30:00 158 True False

11987 rows × 3 columns

2. Dividir el conjunto de datos¶

In [499]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [500]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.5))
model.add(LSTM(64))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')
Model: "sequential_63"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv1d_19 (Conv1D)          (None, 6, 32)             320       
                                                                 
 batch_normalization_38 (Bat  (None, 6, 32)            128       
 chNormalization)                                                
                                                                 
 max_pooling1d_19 (MaxPoolin  (None, 3, 32)            0         
 g1D)                                                            
                                                                 
 dropout_38 (Dropout)        (None, 3, 32)             0         
                                                                 
 lstm_73 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_39 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_39 (Dropout)        (None, 64)                0         
                                                                 
 dense_63 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 25,601
Trainable params: 25,409
Non-trainable params: 192
_________________________________________________________________

4. Entrenar el modelo¶

In [501]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1606/1606 [==============================] - 9s 4ms/step - loss: 43.6550 - val_loss: 33.4275
Epoch 2/20
1606/1606 [==============================] - 7s 4ms/step - loss: 24.3415 - val_loss: 62.3027
Epoch 3/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.8099 - val_loss: 20.3992
Epoch 4/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.7185 - val_loss: 22.0949
Epoch 5/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.4632 - val_loss: 50.1489
Epoch 6/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.3739 - val_loss: 10.8982
Epoch 7/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.2964 - val_loss: 20.7282
Epoch 8/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.1620 - val_loss: 21.4562
Epoch 9/20
1606/1606 [==============================] - 7s 4ms/step - loss: 23.1282 - val_loss: 13.8787

5. Evaluar¶

In [502]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_CRNN_24 = train_eval
val_eval_measurement_hypo_hype_3_CRNN_24 = val_eval
test_eval_measurement_hypo_hype_3_CRNN_24 = test_eval

train_rmse_measurement_hypo_hype_3_CRNN_24 = train_rmse
val_rmse_measurement_hypo_hype_3_CRNN_24 = val_rmse
test_rmse_measurement_hypo_hype_3_CRNN_24 = test_rmse
3211/3211 [==============================] - 4s 1ms/step
402/402 [==============================] - 0s 1ms/step
402/402 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 15.693042755126953
Evaluación Validación MAE: 13.87879467010498
Evaluación Prueba MAE: 18.05121421813965
Entrenamiento RMSE: 19.069278717041016
Validación RMSE: 14.048263549804688
Prueba RMSE: 18.586702346801758

6. Gráfica¶

In [503]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [504]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 25ms/step

                     Datos predichos
date                                
2022-03-12 08:45:00       172.894638

Distintas arquitecturas CRNN probadas¶

CRNN_ 2¶

In [187]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_24[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(64, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_2_CRNN_24 = train_eval
val_eval_measurement_hypo_hype_2_CRNN_24 = val_eval
test_eval_measurement_hypo_hype_2_CRNN_24 = test_eval

train_rmse_measurement_hypo_hype_2_CRNN_24 = train_rmse
val_rmse_measurement_hypo_hype_2_CRNN_24 = val_rmse
test_rmse_measurement_hypo_hype_2_CRNN_24 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1606/1606 [==============================] - 5s 3ms/step - loss: 127.7551 - val_loss: 110.0686
Epoch 2/20
1606/1606 [==============================] - 4s 2ms/step - loss: 86.4737 - val_loss: 68.8918
Epoch 3/20
1606/1606 [==============================] - 4s 2ms/step - loss: 47.2744 - val_loss: 29.7448
Epoch 4/20
1606/1606 [==============================] - 4s 2ms/step - loss: 26.7348 - val_loss: 16.7949
Epoch 5/20
1606/1606 [==============================] - 4s 2ms/step - loss: 21.8089 - val_loss: 12.6721
Epoch 6/20
1606/1606 [==============================] - 4s 2ms/step - loss: 24.1162 - val_loss: 15.8789
Epoch 7/20
1606/1606 [==============================] - 4s 2ms/step - loss: 20.7076 - val_loss: 12.6449
Epoch 8/20
1606/1606 [==============================] - 4s 2ms/step - loss: 30.5231 - val_loss: 21.9252
Epoch 9/20
1606/1606 [==============================] - 4s 2ms/step - loss: 22.7142 - val_loss: 12.8030
Epoch 10/20
1606/1606 [==============================] - 4s 2ms/step - loss: 31.3560 - val_loss: 28.4509
3211/3211 [==============================] - 3s 907us/step
402/402 [==============================] - 0s 909us/step
402/402 [==============================] - 0s 924us/step
Evaluación Entrenamiento MAE: 29.41339111328125
Evaluación Validación MAE: 28.45074462890625
Evaluación Prueba MAE: 34.331600189208984
Entrenamiento RMSE: 37.38134765625
Validación RMSE: 31.26727294921875
Prueba RMSE: 35.256107330322266

CRNN_1¶

In [188]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_24[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_1_CRNN_24 = train_eval
val_eval_measurement_hypo_hype_1_CRNN_24 = val_eval
test_eval_measurement_hypo_hype_1_CRNN_24 = test_eval

train_rmse_measurement_hypo_hype_1_CRNN_24 = train_rmse
val_rmse_measurement_hypo_hype_1_CRNN_24 = val_rmse
test_rmse_measurement_hypo_hype_1_CRNN_24 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1606/1606 [==============================] - 5s 2ms/step - loss: 130.0658 - val_loss: 115.2105
Epoch 2/20
1606/1606 [==============================] - 3s 2ms/step - loss: 89.2542 - val_loss: 70.9465
Epoch 3/20
1606/1606 [==============================] - 3s 2ms/step - loss: 49.0223 - val_loss: 31.4324
Epoch 4/20
1606/1606 [==============================] - 4s 2ms/step - loss: 26.7361 - val_loss: 15.3060
Epoch 5/20
1606/1606 [==============================] - 3s 2ms/step - loss: 28.0950 - val_loss: 17.8022
Epoch 6/20
1606/1606 [==============================] - 3s 2ms/step - loss: 22.5359 - val_loss: 12.7463
Epoch 7/20
1606/1606 [==============================] - 3s 2ms/step - loss: 27.6685 - val_loss: 16.8703
Epoch 8/20
1606/1606 [==============================] - 3s 2ms/step - loss: 25.3135 - val_loss: 17.1612
Epoch 9/20
1606/1606 [==============================] - 3s 2ms/step - loss: 26.8881 - val_loss: 18.9814
3211/3211 [==============================] - 3s 872us/step
402/402 [==============================] - 0s 888us/step
402/402 [==============================] - 0s 862us/step
Evaluación Entrenamiento MAE: 23.21613883972168
Evaluación Validación MAE: 18.981304168701172
Evaluación Prueba MAE: 24.862287521362305
Entrenamiento RMSE: 31.618885040283203
Validación RMSE: 22.98874855041504
Prueba RMSE: 26.123821258544922

Resultado obtenido con 3 configuraciones de hiperparámetros CRNN¶

Después de realizar 3 pruebas se puede determinar que configuración de hiperparámetros del modelo CRNN y con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura CRNN, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas, siendo en este caso la Measurement + Hypoglycemia + Hyperglycemia.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura CRNN y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA CRNN_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [505]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_CRNN vacía
tabla_1_CRNN_24 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_CRNN_24.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_CRNN_24.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_CRNN_24.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_CRNN_24.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_CRNN_24.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_CRNN_24.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_CRNN_24.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_CRNN_24
tabla_1_CRNN_24.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_CRNN_24
tabla_1_CRNN_24.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_CRNN_24

tabla_1_CRNN_24.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_CRNN_24
tabla_1_CRNN_24.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_CRNN_24
tabla_1_CRNN_24.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_CRNN_24

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_CRNN_24.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 23.216139
Validación MAE 18.981304
Test MAE 24.862288
Entrenamiento RMSE 31.618885
Validación RMSE 22.988749
Test RMSE 26.123821
In [506]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_CRNN_24.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_CRNN_24 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_CRNN_24 = pd.concat([tabla_1_CRNN_24, pd.DataFrame([row])])

TABLA CRNN_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 64. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [507]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_2_CRNN vacía
tabla_2_CRNN_24 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_CRNN_24.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_CRNN_24.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_CRNN_24.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_CRNN_24.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_CRNN_24.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_CRNN_24.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_CRNN_24.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_CRNN_24
tabla_2_CRNN_24.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_CRNN_24
tabla_2_CRNN_24.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_CRNN_24

tabla_2_CRNN_24.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_CRNN_24
tabla_2_CRNN_24.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_CRNN_24
tabla_2_CRNN_24.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_CRNN_24

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_CRNN_24.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 29.413391
Validación MAE 28.450745
Test MAE 34.331600
Entrenamiento RMSE 37.381348
Validación RMSE 31.267273
Test RMSE 35.256107
In [508]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_CRNN_24.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_CRNN_24 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_CRNN_24 = pd.concat([tabla_2_CRNN_24, pd.DataFrame([row])])

TABLA CRNN_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [509]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_CRNN vacía
tabla_3_CRNN_24 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_CRNN_24.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_CRNN_24.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_CRNN_24.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_CRNN_24.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_CRNN_24.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_CRNN_24.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_CRNN_24.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_CRNN_24
tabla_3_CRNN_24.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_CRNN_24
tabla_3_CRNN_24.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_CRNN_24

tabla_3_CRNN_24.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_CRNN_24
tabla_3_CRNN_24.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_CRNN_24
tabla_3_CRNN_24.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_CRNN_24

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_CRNN_24.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 15.693043
Validación MAE 13.878795
Test MAE 18.051214
Entrenamiento RMSE 19.069279
Validación RMSE 14.048264
Test RMSE 18.586702
In [510]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_CRNN_24.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_CRNN_24 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_CRNN_24 = pd.concat([tabla_3_CRNN_24, pd.DataFrame([row])])

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3¶

In [511]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_CRNN_24, tabla_2_CRNN_24, tabla_3_CRNN_24], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 23.216139 Measurement + Hypo_Hyper Entrenamiento MAE 29.413391 Measurement + Hypo_Hyper Entrenamiento MAE 15.693043 Measurement + Hypo_Hyper
Validación MAE 18.981304 Measurement + Hypo_Hyper Validación MAE 28.450745 Measurement + Hypo_Hyper Validación MAE 13.878795 Measurement + Hypo_Hyper
Test MAE 24.862288 Measurement + Hypo_Hyper Test MAE 34.331600 Measurement + Hypo_Hyper Test MAE 18.051214 Measurement + Hypo_Hyper
Entrenamiento RMSE 31.618885 Measurement + Hypo_Hyper Entrenamiento RMSE 37.381348 Measurement + Hypo_Hyper Entrenamiento RMSE 19.069279 Measurement + Hypo_Hyper
Validación RMSE 22.988749 Measurement + Hypo_Hyper Validación RMSE 31.267273 Measurement + Hypo_Hyper Validación RMSE 14.048264 Measurement + Hypo_Hyper
Test RMSE 26.123821 Measurement + Hypo_Hyper Test RMSE 35.256107 Measurement + Hypo_Hyper Test RMSE 18.586702 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR CRNN_1, CRNN_2 y CRNN_3¶

In [512]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'CRNN'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_CRNN_24.loc[tabla_1_CRNN_24['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_CRNN_24.loc[tabla_1_CRNN_24['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_CRNN_24.loc[tabla_1_CRNN_24['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_CRNN_24.loc[tabla_2_CRNN_24['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_CRNN_24.loc[tabla_2_CRNN_24['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_CRNN_24.loc[tabla_2_CRNN_24['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_CRNN_24.loc[tabla_3_CRNN_24['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_CRNN_24.loc[tabla_3_CRNN_24['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_CRNN_24.loc[tabla_3_CRNN_24['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'CRNN': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO CRNN</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>CRNN_3 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
CRNN_24 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{CRNN_24}</div>'))

MEJOR RESULTADO CRNN

CRNN_3
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 15.693043 Measurement + Hypo_Hyper 3
Validación MAE 13.878795 Measurement + Hypo_Hyper 3
Test MAE 18.051214 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 19.069279 Measurement + Hypo_Hyper 3
Validación RMSE 14.048264 Measurement + Hypo_Hyper 3
Test RMSE 18.586702 Measurement + Hypo_Hyper 3

Conclusión CRNN¶

La Red Neuronal Recurrente Convolucional (CRNN) es una arquitectura de red neuronal que combina características de las redes neuronales convolucionales (CNN) y las redes neuronales recurrentes (RNN). Esta diseñada para modelar y aprender patrones en datos secuenciales, al tiempo que tiene en cuenta la estructura espacial de los datos, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

Al igual que las CNN, las CRNN utilizan capas convolucionales para extraer características locales de los datos de entrada. Estas capas convolucionales aplican filtros a ventanas de tamaño fijo, que se deslizan a lo largo de la secuencia para detectar patrones locales. Esto permite a la CRNN capturar características relevantes en diferentes partes de la secuencia de datos.

Sin embargo, a diferencia de las CNN tradicionales, las CRNN también incorporan la capacidad de modelar dependencias a largo plazo utilizando unidades recurrentes, como las LSTM. Las LSTM en la CRNN actúan como una capa de procesamiento secuencial adicional que se aplica después de las capas convolucionales. Estas unidades recurrentes permiten que la red mantenga una memoria de largo plazo y capture dependencias a largo plazo en la secuencia.

La combinación de capas convolucionales y unidades LSTM en una CRNN aprovecha tanto la capacidad de las CNN para extraer características locales como la capacidad de las RNN para modelar dependencias a largo plazo. Las capas convolucionales ayudan a capturar patrones locales en la secuencia, mientras que las unidades LSTM permiten que la red aprenda y recuerde dependencias a largo plazo entre los elementos de la secuencia.

En resumen, una CRNN es una arquitectura de red neuronal que combina capas convolucionales y unidades LSTM para modelar y aprender patrones en datos secuenciales. Las capas convolucionales extraen características locales de la secuencia, mientras que las unidades LSTM capturan dependencias a largo plazo. Esto permite a la CRNN capturar patrones complejos y modelar tanto la estructura espacial como las dependencias temporales en los datos secuenciales.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo CRNN es entrenado, se puede determinar que la CRNN_3 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global CRNN:

  • Mejor CRNN 3:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor CRNN es la 3 con las características de entrada Measurement + Hypo_Hyper.

Comparativa de resultados LSTM y CRNN¶

In [513]:
from IPython.display import display, HTML

html_tables_24 = f"""
<style>
    .main-container {{
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
    }}
    .table-wrapper {{
        display: inline-block;
        font-size: 14px;
        vertical-align: top;
        margin-right: 20px;
    }}
    .table-wrapper table {{
        font-size: 14px;
    }}
    .table-wrapper th, .table-wrapper td {{
        width: 100px;
        text-align: center;
    }}
    .table-wrapper h3 {{
        text-align: center;
        font-size: 18px;
        color: blue;
    }}
    h2 {{
        text-align: center;
    }}
</style>
<h2>Paciente 24</h2>
<div class="main-container">
    <div class="table-wrapper">
        <h3>LSTM</h3>
        {LSTM_24}
    </div>
    <div class="table-wrapper">
        <h3>CRNN</h3>
        {CRNN_24}
    </div>
</div>
"""

display(HTML(html_tables_24))

Paciente 24

LSTM

Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.348582 Measurement + Hypo_Hyper 2
Validación MAE 0.489993 Measurement + Hypo_Hyper 2
Test MAE 0.520977 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 4.971605 Measurement + Hypo_Hyper 2
Validación RMSE 0.733889 Measurement + Hypo_Hyper 2
Test RMSE 0.772963 Measurement + Hypo_Hyper 2

CRNN

Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 15.693043 Measurement + Hypo_Hyper 3
Validación MAE 13.878795 Measurement + Hypo_Hyper 3
Test MAE 18.051214 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 19.069279 Measurement + Hypo_Hyper 3
Validación RMSE 14.048264 Measurement + Hypo_Hyper 3
Test RMSE 18.586702 Measurement + Hypo_Hyper 3

Mejor Modelo (Paciente 24)¶

In [514]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'Arquitectura', 'Configuración Hiperparámetros'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Diccionarios para almacenar los nombres de las tablas
tables_arch1 = {'tabla_1_CRNN_24': 1, 'tabla_2_CRNN_24': 2, 'tabla_3_CRNN_24': 3}
tables_arch2 = {'tabla_1_LSTM_24': 1, 'tabla_2_LSTM_24': 2, 'tabla_3_LSTM_24': 3}

# Para cada fila
for row in rows:
    min_vals = []
    
    # Arquitectura 1
    for table_name, arch in tables_arch1.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'CRNN', arch))

    # Arquitectura 2
    for table_name, arch in tables_arch2.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'LSTM', arch))

    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val, min_col, min_arch, min_num = min(min_vals, key=lambda x: x[0])

    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')

    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'Arquitectura': [min_arch],
        'Configuración Hiperparámetros': [min_num]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px; font-weight:bold;'>MEJOR ARQUITECTURA (Paciente 24)</span></center>"
display(HTML(html_text))

# Aplica el estilo personalizado al DataFrame resultante
styled_result = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_result = styled_result.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Mostrar el DataFrame resultante con estilo y sin índices
final_output = styled_result.hide(axis='index').to_html()

# Utilizando la función `display(HTML())` para mostrar el contenido HTML en un div centrado en la página.
display(HTML("<div style='margin: 0 auto; width:70%'>" + final_output + "</div>"))

# Nombrando
styled_result2 = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])
styled_result2 = styled_result2.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Muestra la predicción del nivel de glucosa en los próximos 15 minutos
# Crea un HTML con el título y los datos

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_table = future_predictions_hypo_hype_2_LSTM_24.style.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

html = """
<div style='text-align: center;'>
<h2>Predicción del nivel de glucosa en los próximos 15 minutos</h2>
<div style='margin: 20px auto 0 40%; width:40%;'>
""" + styled_table.to_html() + "</div></div>"

# Muestra el HTML
display(HTML(html))

# Centrar la figura y la tabla utilizando CSS y HTML
display(HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
<h2>Precisión del modelo con Clarke Error Grid</h2>
"""))


# Mostrar la figura
display(fig_hypo_hype_2_LSTM_24)

# Centrar el contenido de la tabla utilizando CSS y ocultar los índices
styled_table = zone_df_hypo_hype_2_LSTM_24.style.set_properties(**{'text-align': 'center'}).hide(axis='index')

# Convertir la tabla en un objeto HTML
table_html = f"<center>{styled_table.to_html()}</center>"

# Mostrar la tabla centrada
display(HTML(table_html))

MEJOR ARQUITECTURA (Paciente 24)
Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.348582 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.489993 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.520977 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 4.971605 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 0.733889 Measurement + Hypo_Hyper LSTM 2
Test RMSE 0.772963 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-12 08:45:00 158.111694

Precisión del modelo con Clarke Error Grid

Zona Conteo Proporción
A 12836 100.00%
B 0 0.00%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Conclusión Paciente 24¶

Los resultados nos aclara que la mejor opción para el paciente 24 es utilizar una arquitectura LSTM con una configuración de hiperparámetros 2.

Arquitectura LSTM con la LSTM_2 global:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

La conclusión final para el paciente 24 es utilizar la arquitectura LSTM con la configuración de hiperparámetros y las características de entrada Measurement + Hypo_Hyper para la predicción de sus niveles de glucosa para los próximos 15 minutos ya que nos arrojan unos resultados muy buenos.

Paciente 11
¶

La comparativa de soluciones con los modelos RNN y CRNN se va a realizar para el paciente 11 debido a que tras un análisis extenso se ha comprobado que es el 2º que tiene más mediciones.

Se va a llevar a cabo la comparativa de soluciones con los modelos RNN y CRNN pero con la mejor solución ya obtenida con el paciente 22. De esta manera se va a trabajar con la mejor arquitectura de hiperparámetros y de características de entrada así se va a poder confirmar que el experimento se ha realizado con éxito.

La característica de entrada va a ser: 'Measurement + Hypo_Hyper'

In [199]:
# Filtrar los datos para el paciente 11
df_paciente_11 = df_top_5_pacientes[df_top_5_pacientes['Patient_ID'] == 11]

# Imprimir los resultados
df_paciente_11
Out[199]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-06-12 20:15:00 11 2018-06-12 1900-01-01 20:18:00 178 True False False 20 1
2018-06-12 20:30:00 11 2018-06-12 1900-01-01 20:33:00 178 True False False 20 1
2018-06-12 20:45:00 11 2018-06-12 1900-01-01 20:48:00 166 True False False 20 1
2018-06-12 21:00:00 11 2018-06-12 1900-01-01 21:03:00 157 True False False 21 1
2018-06-12 21:15:00 11 2018-06-12 1900-01-01 21:17:00 131 True False False 21 1
... ... ... ... ... ... ... ... ... ...
2022-02-25 10:30:00 11 2022-02-25 1900-01-01 10:30:00 174 True False False 10 4
2022-02-25 11:00:00 11 2022-02-25 1900-01-01 11:00:00 171 True False False 11 4
2022-02-25 11:15:00 11 2022-02-25 1900-01-01 11:15:00 167 True False False 11 4
2022-02-25 11:30:00 11 2022-02-25 1900-01-01 11:30:00 164 True False False 11 4
2022-02-25 19:15:00 11 2022-02-25 1900-01-01 19:15:00 178 True False False 19 4

11332 rows × 9 columns

Red Neuronal Recurrente (RNN) con el algoritmo Long Short-Term Memory (LSTM)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo LSTM con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Mesurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hyperglycemia" indica si una medición está por encima del rango normal.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [200]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_11[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [201]:
df
Out[201]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-06-12 20:18:00 178 False False
2018-06-12 20:33:00 178 False False
2018-06-12 20:48:00 166 False False
2018-06-12 21:03:00 157 False False
2018-06-12 21:17:00 131 False False
... ... ... ...
2022-02-25 10:30:00 174 False False
2022-02-25 11:00:00 171 False False
2022-02-25 11:15:00 167 False False
2022-02-25 11:30:00 164 False False
2022-02-25 19:15:00 178 False False

11332 rows × 3 columns

2. Dividir el conjunto de datos¶

In [202]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [203]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_27"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_34 (LSTM)              (None, 64)                17408     
                                                                 
 dense_27 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,473
Trainable params: 17,473
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [204]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1625/1625 [==============================] - 9s 5ms/step - loss: 3.5663 - val_loss: 0.4989
Epoch 2/20
1625/1625 [==============================] - 8s 5ms/step - loss: 2.4879 - val_loss: 0.4127
Epoch 3/20
1625/1625 [==============================] - 8s 5ms/step - loss: 2.1747 - val_loss: 1.6120
Epoch 4/20
1625/1625 [==============================] - 8s 5ms/step - loss: 2.0618 - val_loss: 0.7492
Epoch 5/20
1625/1625 [==============================] - 8s 5ms/step - loss: 1.8942 - val_loss: 0.8327

5. Evaluar¶

In [205]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_2_LSTM_11 = train_eval
val_eval_measurement_hypo_hype_2_LSTM_11 = val_eval
test_eval_measurement_hypo_hype_2_LSTM_11 = test_eval

train_rmse_measurement_hypo_hype_2_LSTM_11 = train_rmse
val_rmse_measurement_hypo_hype_2_LSTM_11 = val_rmse
test_rmse_measurement_hypo_hype_2_LSTM_11 = test_rmse
3250/3250 [==============================] - 5s 1ms/step
406/406 [==============================] - 1s 1ms/step
406/406 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.6652015447616577
Evaluación Validación MAE: 0.8327163457870483
Evaluación Prueba MAE: 0.8226503133773804
Entrenamiento RMSE: 5.385063648223877
Validación RMSE: 1.542349100112915
Prueba RMSE: 1.477170705795288

6. Gráfica¶

In [206]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [207]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')

future_predictions_hypo_hype_2_LSTM_11 = future_predictions
plt.show()
1/1 [==============================] - 0s 14ms/step

                     Datos predichos
date                                
2022-02-25 19:30:00       169.183929

6.2 Clarke Error Grid¶

In [208]:
def clarke_error_grid(ref_values, pred_values):
    assert (len(ref_values) == len(pred_values)), "Unequal number of values (reference: {}) (prediction: {}).".format(len(ref_values), len(pred_values))

    if max(ref_values) > 400 or max(pred_values) > 400:
        print("Input Warning: the maximum reference value {} or the maximum prediction value {} exceeds the normal physiological range of glucose (<400 mg/dl).".format(max(ref_values), max(pred_values)))
    if min(ref_values) < 0 or min(pred_values) < 0:
        print("Input Warning: the minimum reference value {} or the minimum prediction value {} is less than 0 mg/dl.".format(min(ref_values),  min(pred_values)))

    plt.clf()
    plt.scatter(ref_values, pred_values, marker='o', color='black', s=8)
    plt.title("Clarke Error Grid")
    plt.xlabel("Reference Concentration (mg/dl)")
    plt.ylabel("Prediction Concentration (mg/dl)")
    plt.xticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.yticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.gca().set_facecolor('white')
    plt.gca().set_xlim([0, 400])
    plt.gca().set_ylim([0, 400])
    plt.gca().set_aspect((400)/(400))
    plt.plot([0, 400], [0, 400], ':', c='black')
    plt.plot([0, 175/3], [70, 70], '-', c='black')
    plt.plot([175/3, 400/1.2], [70, 400], '-', c='black')
    plt.plot([70, 70], [84, 400],'-', c='black')
    plt.plot([0, 70], [180, 180], '-', c='black')
    plt.plot([70, 290], [180, 400],'-', c='black')
    plt.plot([70, 70], [0, 56], '-', c='black')
    plt.plot([70, 400], [56, 320],'-', c='black')
    plt.plot([180, 180], [0, 70], '-', c='black')
    plt.plot([180, 400], [70, 70], '-', c='black')
    plt.plot([240, 240], [70, 180],'-', c='black')
    plt.plot([240, 400], [180, 180], '-', c='black')
    plt.plot([130, 180], [0, 70], '-', c='black')
    plt.text(30, 15, "A", fontsize=15)
    plt.text(370, 260, "B", fontsize=15)
    plt.text(280, 370, "B", fontsize=15)
    plt.text(160, 370, "C", fontsize=15)
    plt.text(160, 15, "C", fontsize=15)
    plt.text(30, 140, "D", fontsize=15)
    plt.text(370, 120, "D", fontsize=15)
    plt.text(30, 370, "E", fontsize=15)
    plt.text(370, 15, "E", fontsize=15)

    zone = [0] * 5
    for i in range(len(ref_values)):
        if (ref_values[i] <= 70 and pred_values[i] <= 70) or (pred_values[i] <= 1.2 * ref_values[i] and pred_values[i] >= 0.8 * ref_values[i]):
            zone[0] += 1  # Zone A
        elif (ref_values[i] >= 180 and pred_values[i] <= 70) or (ref_values[i] <= 70 and pred_values[i] >= 180):
            zone[4] += 1  # Zone E
        elif ((ref_values[i] >= 70 and ref_values[i] <= 290) and pred_values[i] >= ref_values[i] + 110) or ((ref_values[i] >= 130 and ref_values[i] <= 180) and (pred_values[i] <= (7/5) * ref_values[i] - 182)):
            zone[2] += 1  # Zone C
        elif (ref_values[i] >= 240 and (pred_values[i] >= 70 and pred_values[i] <= 180)) or (ref_values[i] <= 175/3 and pred_values[i] <= 180 and pred_values[i] >= 70) or ((ref_values[i] >= 175/3 and ref_values[i] <= 70) and pred_values[i] >= (6/5) * ref_values[i]):
            zone[3] += 1  # Zone D
        else:
            zone[1] += 1  # Zone B

    return plt, zone

ref_values = y_test
pred_values = y_pred_test

plt, zone = clarke_error_grid(ref_values, pred_values)

# Asignar la figura a una variable antes de llamar a plt.show()
fig_hypo_hype_2_LSTM_11 = plt.gcf()

# Mostrar la figura
plt.show()

# Crear DataFrame para visualizar el conteo de zonas
zone_df = pd.DataFrame({'Zona': ['A', 'B', 'C', 'D', 'E'], 'Conteo': zone})

# Calcular la proporción de cada zona respecto al total
total = sum(zone)
zone_df['Proporción'] = zone_df['Conteo'] / total

# Mostrar la proporción en porcentaje
zone_df['Proporción'] = zone_df['Proporción'].apply(lambda x: '{:.2f}%'.format(x * 100))

# Asignar el DataFrame a una variable
zone_df_hypo_hype_2_LSTM_11 = zone_df

# Ocultar el índice y mostrar el DataFrame
print("Conteo de Zonas:")
display(zone_df_hypo_hype_2_LSTM_11.style.hide(axis="index"))
Conteo de Zonas:
Zona Conteo Proporción
A 12989 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [209]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_11[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')


# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_1_LSTM_11 = train_eval
val_eval_measurement_hypo_hype_1_LSTM_11 = val_eval
test_eval_measurement_hypo_hype_1_LSTM_11 = test_eval

train_rmse_measurement_hypo_hype_1_LSTM_11 = train_rmse
val_rmse_measurement_hypo_hype_1_LSTM_11 = val_rmse
test_rmse_measurement_hypo_hype_1_LSTM_11 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_28"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_35 (LSTM)              (None, 32)                4608      
                                                                 
 dense_28 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,641
Trainable params: 4,641
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1625/1625 [==============================] - 6s 3ms/step - loss: 127.7843 - val_loss: 127.5787
Epoch 2/20
1625/1625 [==============================] - 5s 3ms/step - loss: 95.5822 - val_loss: 94.1824
Epoch 3/20
1625/1625 [==============================] - 5s 3ms/step - loss: 64.1425 - val_loss: 63.2005
Epoch 4/20
1625/1625 [==============================] - 5s 3ms/step - loss: 47.7202 - val_loss: 47.1324
Epoch 5/20
1625/1625 [==============================] - 5s 3ms/step - loss: 35.3631 - val_loss: 24.0392
Epoch 6/20
1625/1625 [==============================] - 5s 3ms/step - loss: 18.7995 - val_loss: 14.1535
Epoch 7/20
1625/1625 [==============================] - 5s 3ms/step - loss: 9.9529 - val_loss: 6.9633
Epoch 8/20
1625/1625 [==============================] - 5s 3ms/step - loss: 6.0866 - val_loss: 4.1781
Epoch 9/20
1625/1625 [==============================] - 5s 3ms/step - loss: 4.9730 - val_loss: 2.4350
Epoch 10/20
1625/1625 [==============================] - 5s 3ms/step - loss: 3.8005 - val_loss: 1.1713
Epoch 11/20
1625/1625 [==============================] - 5s 3ms/step - loss: 3.2813 - val_loss: 0.7460
Epoch 12/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.7940 - val_loss: 0.6772
Epoch 13/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.5765 - val_loss: 0.5412
Epoch 14/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.4196 - val_loss: 0.8206
Epoch 15/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.2702 - val_loss: 1.5510
Epoch 16/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.2506 - val_loss: 0.4245
Epoch 17/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.1151 - val_loss: 0.4636
Epoch 18/20
1625/1625 [==============================] - 5s 3ms/step - loss: 2.1483 - val_loss: 0.5238
Epoch 19/20
1625/1625 [==============================] - 5s 3ms/step - loss: 1.9364 - val_loss: 1.0281
3250/3250 [==============================] - 4s 1ms/step
406/406 [==============================] - 0s 1ms/step
406/406 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 1.7831400632858276
Evaluación Validación MAE: 1.0280921459197998
Evaluación Prueba MAE: 1.0847498178482056
Entrenamiento RMSE: 5.725601673126221
Validación RMSE: 1.5745826959609985
Prueba RMSE: 1.5618395805358887

LSTM_3¶

In [210]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_11[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_LSTM_11 = train_eval
val_eval_measurement_hypo_hype_3_LSTM_11 = val_eval
test_eval_measurement_hypo_hype_3_LSTM_11 = test_eval

train_rmse_measurement_hypo_hype_3_LSTM_11 = train_rmse
val_rmse_measurement_hypo_hype_3_LSTM_11 = val_rmse
test_rmse_measurement_hypo_hype_3_LSTM_11 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_29"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_36 (LSTM)              (None, 8, 32)             4608      
                                                                 
 batch_normalization_18 (Bat  (None, 8, 32)            128       
 chNormalization)                                                
                                                                 
 dropout_18 (Dropout)        (None, 8, 32)             0         
                                                                 
 lstm_37 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_19 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_19 (Dropout)        (None, 64)                0         
                                                                 
 dense_29 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,889
Trainable params: 29,697
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1625/1625 [==============================] - 14s 7ms/step - loss: 41.6129 - val_loss: 10.1136
Epoch 2/20
1625/1625 [==============================] - 12s 7ms/step - loss: 25.4291 - val_loss: 15.1393
Epoch 3/20
1625/1625 [==============================] - 12s 7ms/step - loss: 24.9196 - val_loss: 9.7338
Epoch 4/20
1625/1625 [==============================] - 12s 7ms/step - loss: 24.5823 - val_loss: 11.0667
Epoch 5/20
1625/1625 [==============================] - 12s 7ms/step - loss: 24.5072 - val_loss: 6.8025
Epoch 6/20
1625/1625 [==============================] - 12s 7ms/step - loss: 24.3224 - val_loss: 13.4693
Epoch 7/20
1625/1625 [==============================] - 12s 7ms/step - loss: 24.2720 - val_loss: 17.2260
Epoch 8/20
1625/1625 [==============================] - 12s 7ms/step - loss: 24.2577 - val_loss: 9.0709
3250/3250 [==============================] - 7s 2ms/step
406/406 [==============================] - 1s 2ms/step
406/406 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 8.484620094299316
Evaluación Validación MAE: 9.070947647094727
Evaluación Prueba MAE: 10.522764205932617
Entrenamiento RMSE: 11.441686630249023
Validación RMSE: 9.630925178527832
Prueba RMSE: 10.921760559082031

Resultado obtenido con 3 configuraciones de hiperparámetros LSTM¶

Después de realizar las pruebas se puede determinar que configuración de hiperparámetros del modelo LSTMy con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura LSTM, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura LSTM y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA LSTM_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [211]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_LSTM_11 vacía
tabla_1_LSTM_11 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_LSTM_11.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_LSTM_11.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_LSTM_11.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_LSTM_11.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_LSTM_11.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_LSTM_11.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_LSTM_11.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_LSTM_11
tabla_1_LSTM_11.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_LSTM_11
tabla_1_LSTM_11.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_LSTM_11

tabla_1_LSTM_11.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_LSTM_11
tabla_1_LSTM_11.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_LSTM_11
tabla_1_LSTM_11.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_LSTM_11

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_LSTM_11.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 1.783140
Validación MAE 1.028092
Test MAE 1.084750
Entrenamiento RMSE 5.725602
Validación RMSE 1.574583
Test RMSE 1.561840
In [212]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_LSTM_11.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_LSTM_11 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_LSTM_11 = pd.concat([tabla_1_LSTM_11, pd.DataFrame([row])])

TABLA LSTM_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [213]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla_2_LSTM_11 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_LSTM_11.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_LSTM_11.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_LSTM_11.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_LSTM_11.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_LSTM_11.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_LSTM_11.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_LSTM_11.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_LSTM_11
tabla_2_LSTM_11.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_LSTM_11
tabla_2_LSTM_11.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_LSTM_11

tabla_2_LSTM_11.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_LSTM_11
tabla_2_LSTM_11.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_LSTM_11
tabla_2_LSTM_11.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_LSTM_11

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_LSTM_11.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 1.665202
Validación MAE 0.832716
Test MAE 0.822650
Entrenamiento RMSE 5.385064
Validación RMSE 1.542349
Test RMSE 1.477171
In [214]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_LSTM_11.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_LSTM_11 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_LSTM_11 = pd.concat([tabla_2_LSTM_11, pd.DataFrame([row])])

TABLA LSTM_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • return_sequences: True.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5.
  • units: 64. Número de neuronas en la capa LSTM.
  • return_sequences: False. True/False. Determina si una capa LSTM devuelve la secuencia completa de salidas o solo la última; 'True' devuelve toda la secuencia, 'False' solo la última salida.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001). Aplica penalizaciones a los coeficientes de la capa durante el entrenamiento, en este caso utilizando la regularización L1 y L2, lo que puede ayudar a prevenir el sobreajuste.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [215]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_LSTM_11 vacía
tabla_3_LSTM_11 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_LSTM_11.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_LSTM_11.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_LSTM_11.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_LSTM_11.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_LSTM_11.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_LSTM_11.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_LSTM_11.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_LSTM_11
tabla_3_LSTM_11.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_LSTM_11
tabla_3_LSTM_11.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_LSTM_11

tabla_3_LSTM_11.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_LSTM_11
tabla_3_LSTM_11.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_LSTM_11
tabla_3_LSTM_11.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_LSTM_11

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_LSTM_11.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 8.484620
Validación MAE 9.070948
Test MAE 10.522764
Entrenamiento RMSE 11.441687
Validación RMSE 9.630925
Test RMSE 10.921761
In [216]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_LSTM_11.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_LSTM_11 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_LSTM_11 = pd.concat([tabla_3_LSTM_11, pd.DataFrame([row])])

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3¶

In [217]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_LSTM_11, tabla_2_LSTM_11, tabla_3_LSTM_11], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 1.783140 Measurement + Hypo_Hyper Entrenamiento MAE 1.665202 Measurement + Hypo_Hyper Entrenamiento MAE 8.484620 Measurement + Hypo_Hyper
Validación MAE 1.028092 Measurement + Hypo_Hyper Validación MAE 0.832716 Measurement + Hypo_Hyper Validación MAE 9.070948 Measurement + Hypo_Hyper
Test MAE 1.084750 Measurement + Hypo_Hyper Test MAE 0.822650 Measurement + Hypo_Hyper Test MAE 10.522764 Measurement + Hypo_Hyper
Entrenamiento RMSE 5.725602 Measurement + Hypo_Hyper Entrenamiento RMSE 5.385064 Measurement + Hypo_Hyper Entrenamiento RMSE 11.441687 Measurement + Hypo_Hyper
Validación RMSE 1.574583 Measurement + Hypo_Hyper Validación RMSE 1.542349 Measurement + Hypo_Hyper Validación RMSE 9.630925 Measurement + Hypo_Hyper
Test RMSE 1.561840 Measurement + Hypo_Hyper Test RMSE 1.477171 Measurement + Hypo_Hyper Test RMSE 10.921761 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR LSTM_1, LSTM_2 y LSTM_3¶

In [218]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'LSTM'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_LSTM_11.loc[tabla_1_LSTM_11['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_LSTM_11.loc[tabla_1_LSTM_11['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_LSTM_11.loc[tabla_1_LSTM_11['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_LSTM_11.loc[tabla_2_LSTM_11['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_LSTM_11.loc[tabla_2_LSTM_11['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_LSTM_11.loc[tabla_2_LSTM_11['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_LSTM_11.loc[tabla_3_LSTM_11['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_LSTM_11.loc[tabla_3_LSTM_11['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_LSTM_11.loc[tabla_3_LSTM_11['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'LSTM': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO LSTM</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>LSTM_2 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
LSTM_11 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{LSTM_11}</div>'))

MEJOR RESULTADO LSTM

LSTM_2
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.665202 Measurement + Hypo_Hyper 2
Validación MAE 0.832716 Measurement + Hypo_Hyper 2
Test MAE 0.822650 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 5.385064 Measurement + Hypo_Hyper 2
Validación RMSE 1.542349 Measurement + Hypo_Hyper 2
Test RMSE 1.477171 Measurement + Hypo_Hyper 2

Conclusión LSTM¶

La LSTM (Long Short-Term Memory) es un tipo de red neuronal recurrente diseñada para modelar y aprender patrones en secuencias de datos. Se utiliza mucho para tareas relacionadas con secuencias, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

La LSTM no sufre degradación del gradiente ni dificultades en la captura de dependencias a largo plazo debido a que utilizan una estructura interna de neuronas que les permite tener una memoria de información relevante a larga plazo y olvidar información obsoleta.

La LSTM tiene neuronas y cada una de las neuronas tiene tres puertas principales:

  1. Puerta de entrada (input gate): Determina qué nueva información debe agregarse al estado actual.
  2. Puerta de olvido (forget gate): Determina qué información antigua debe descartarse del estado actual.
  3. Puerta de salida (output gate): Determina qué parte del estado actual debe emitirse como salida. Además, las puertas contienen funciones de activación.

El diseño de las LSTMs permite que las unidades LSTM mantengan una memoria a largo plazo de la información relevante y eviten que la información se diluya a medida que se procesa a través de la secuencia. Esto las hace especialmente efectivas para modelar dependencias a largo plazo y capturar patrones complejos en datos secuenciales.

En resumen, una LSTM es una unidad recurrente que utiliza una estructura de neuronas con puertas de entrada, olvido y salida para modelar y aprender patrones en secuencias de datos. Su diseño les permite recordar información relevante a largo plazo y superar los problemas de degradación del gradiente, permitiendo un procesamiento efectivo de secuencias.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo LSTM es entrenado, se puede determinar que la LSTM_2 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global LSTM:

  • Mejor LSTM_2:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor LSTM es la 2 con LSTM con las características de entrada Measurement + Hypo_Hyper.

Redes Neuronales Recurrentes Convolucionales (CRNN)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo CRNN con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.¶

En este caso se esta entrenando al algoritmo CRNN con las variables 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal

Mejor arquitectura 3¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo CRNN:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [219]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_11[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [220]:
df
Out[220]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-06-12 20:18:00 178 False False
2018-06-12 20:33:00 178 False False
2018-06-12 20:48:00 166 False False
2018-06-12 21:03:00 157 False False
2018-06-12 21:17:00 131 False False
... ... ... ...
2022-02-25 10:30:00 174 False False
2022-02-25 11:00:00 171 False False
2022-02-25 11:15:00 167 False False
2022-02-25 11:30:00 164 False False
2022-02-25 19:15:00 178 False False

11332 rows × 3 columns

2. Dividir el conjunto de datos¶

In [221]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [222]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.5))
model.add(LSTM(64))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')
Model: "sequential_30"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv1d_6 (Conv1D)           (None, 6, 32)             320       
                                                                 
 batch_normalization_20 (Bat  (None, 6, 32)            128       
 chNormalization)                                                
                                                                 
 max_pooling1d_6 (MaxPooling  (None, 3, 32)            0         
 1D)                                                             
                                                                 
 dropout_20 (Dropout)        (None, 3, 32)             0         
                                                                 
 lstm_38 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_21 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_21 (Dropout)        (None, 64)                0         
                                                                 
 dense_30 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 25,601
Trainable params: 25,409
Non-trainable params: 192
_________________________________________________________________

4. Entrenar el modelo¶

In [223]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1625/1625 [==============================] - 8s 4ms/step - loss: 41.3704 - val_loss: 12.3165
Epoch 2/20
1625/1625 [==============================] - 5s 3ms/step - loss: 25.2367 - val_loss: 22.5381
Epoch 3/20
1625/1625 [==============================] - 5s 3ms/step - loss: 25.1432 - val_loss: 11.8121
Epoch 4/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.8463 - val_loss: 14.8741
Epoch 5/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.9012 - val_loss: 9.6304
Epoch 6/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.6527 - val_loss: 6.4586
Epoch 7/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.6232 - val_loss: 6.0474
Epoch 8/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.4289 - val_loss: 6.6202
Epoch 9/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.3034 - val_loss: 34.1847
Epoch 10/20
1625/1625 [==============================] - 5s 3ms/step - loss: 24.2924 - val_loss: 6.3540

5. Evaluar¶

In [224]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_CRNN_11 = train_eval
val_eval_measurement_hypo_hype_3_CRNN_11 = val_eval
test_eval_measurement_hypo_hype_3_CRNN_11 = test_eval

train_rmse_measurement_hypo_hype_3_CRNN_11 = train_rmse
val_rmse_measurement_hypo_hype_3_CRNN_11 = val_rmse
test_rmse_measurement_hypo_hype_3_CRNN_11 = test_rmse
3250/3250 [==============================] - 4s 1ms/step
406/406 [==============================] - 0s 1ms/step
406/406 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 16.938581466674805
Evaluación Validación MAE: 6.353979587554932
Evaluación Prueba MAE: 7.541975021362305
Entrenamiento RMSE: 22.190229415893555
Validación RMSE: 7.963376522064209
Prueba RMSE: 9.257803916931152

6. Gráfica¶

In [225]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [226]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 12ms/step

                     Datos predichos
date                                
2022-02-25 19:30:00       160.513062

Distintas arquitecturas CRNN probadas¶

CRNN_ 2¶

In [227]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_11[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(64, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_2_CRNN_11 = train_eval
val_eval_measurement_hypo_hype_2_CRNN_11 = val_eval
test_eval_measurement_hypo_hype_2_CRNN_11 = test_eval

train_rmse_measurement_hypo_hype_2_CRNN_11 = train_rmse
val_rmse_measurement_hypo_hype_2_CRNN_11 = val_rmse
test_rmse_measurement_hypo_hype_2_CRNN_11 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1625/1625 [==============================] - 5s 3ms/step - loss: 128.0547 - val_loss: 126.7976
Epoch 2/20
1625/1625 [==============================] - 4s 2ms/step - loss: 92.2619 - val_loss: 88.1347
Epoch 3/20
1625/1625 [==============================] - 4s 2ms/step - loss: 55.9942 - val_loss: 52.6373
Epoch 4/20
1625/1625 [==============================] - 4s 2ms/step - loss: 54.0412 - val_loss: 52.6679
Epoch 5/20
1625/1625 [==============================] - 4s 2ms/step - loss: 54.2733 - val_loss: 54.7867
Epoch 6/20
1625/1625 [==============================] - 4s 2ms/step - loss: 58.1114 - val_loss: 60.2769
3250/3250 [==============================] - 3s 901us/step
406/406 [==============================] - 0s 895us/step
406/406 [==============================] - 0s 898us/step
Evaluación Entrenamiento MAE: 48.0733528137207
Evaluación Validación MAE: 60.2766227722168
Evaluación Prueba MAE: 64.59008026123047
Entrenamiento RMSE: 53.56855392456055
Validación RMSE: 61.03877258300781
Prueba RMSE: 65.19068145751953

CRNN_1¶

In [228]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_11[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_1_CRNN_11 = train_eval
val_eval_measurement_hypo_hype_1_CRNN_11 = val_eval
test_eval_measurement_hypo_hype_1_CRNN_11 = test_eval

train_rmse_measurement_hypo_hype_1_CRNN_11 = train_rmse
val_rmse_measurement_hypo_hype_1_CRNN_11 = val_rmse
test_rmse_measurement_hypo_hype_1_CRNN_11 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1625/1625 [==============================] - 5s 2ms/step - loss: 121.4873 - val_loss: 115.9672
Epoch 2/20
1625/1625 [==============================] - 4s 2ms/step - loss: 81.3642 - val_loss: 76.4117
Epoch 3/20
1625/1625 [==============================] - 4s 2ms/step - loss: 52.5603 - val_loss: 48.7717
Epoch 4/20
1625/1625 [==============================] - 4s 2ms/step - loss: 54.9259 - val_loss: 53.6666
Epoch 5/20
1625/1625 [==============================] - 4s 2ms/step - loss: 52.3760 - val_loss: 56.6435
Epoch 6/20
1625/1625 [==============================] - 4s 2ms/step - loss: 59.1239 - val_loss: 64.2291
3250/3250 [==============================] - 3s 868us/step
406/406 [==============================] - 0s 868us/step
406/406 [==============================] - 0s 884us/step
Evaluación Entrenamiento MAE: 51.451080322265625
Evaluación Validación MAE: 64.22908020019531
Evaluación Prueba MAE: 68.54261016845703
Entrenamiento RMSE: 56.97419738769531
Validación RMSE: 64.94454193115234
Prueba RMSE: 69.10848236083984

Resultado obtenido con 3 configuraciones de hiperparámetros CRNN¶

Después de realizar 3 pruebas se puede determinar que configuración de hiperparámetros del modelo CRNN y con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura CRNN, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas, siendo en este caso la Measurement + Hypoglycemia + Hyperglycemia.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura CRNN y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA CRNN_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [517]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_CRNN vacía
tabla_1_CRNN_11 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_CRNN_11.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_CRNN_11.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_CRNN_11.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_CRNN_11.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_CRNN_11.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_CRNN_11.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_CRNN_11.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_CRNN_11
tabla_1_CRNN_11.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_CRNN_11
tabla_1_CRNN_11.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_CRNN_11

tabla_1_CRNN_11.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_CRNN_11
tabla_1_CRNN_11.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_CRNN_11
tabla_1_CRNN_11.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_CRNN_11

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_CRNN_11.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 51.451080
Validación MAE 64.229080
Test MAE 68.542610
Entrenamiento RMSE 56.974197
Validación RMSE 64.944542
Test RMSE 69.108482
In [518]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_CRNN_11.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_CRNN_11 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_CRNN_11 = pd.concat([tabla_1_CRNN_11, pd.DataFrame([row])])

TABLA CRNN_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 64. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [519]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_2_CRNN vacía
tabla_2_CRNN_11 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_CRNN_11.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_CRNN_11.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_CRNN_11.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_CRNN_11.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_CRNN_11.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_CRNN_11.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_CRNN_11.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_CRNN_11
tabla_2_CRNN_11.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_CRNN_11
tabla_2_CRNN_11.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_CRNN_11

tabla_2_CRNN_11.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_CRNN_11
tabla_2_CRNN_11.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_CRNN_11
tabla_2_CRNN_11.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_CRNN_11

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_CRNN_11.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 48.073353
Validación MAE 60.276623
Test MAE 64.590080
Entrenamiento RMSE 53.568554
Validación RMSE 61.038773
Test RMSE 65.190681
In [520]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_CRNN_11.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_CRNN_11 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_CRNN_11 = pd.concat([tabla_2_CRNN_11, pd.DataFrame([row])])

TABLA CRNN_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [521]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_CRNN vacía
tabla_3_CRNN_11 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_CRNN_11.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_CRNN_11.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_CRNN_11.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_CRNN_11.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_CRNN_11.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_CRNN_11.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_CRNN_11.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_CRNN_11
tabla_3_CRNN_11.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_CRNN_11
tabla_3_CRNN_11.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_CRNN_11

tabla_3_CRNN_11.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_CRNN_11
tabla_3_CRNN_11.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_CRNN_11
tabla_3_CRNN_11.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_CRNN_11

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_CRNN_11.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 16.938581
Validación MAE 6.353980
Test MAE 7.541975
Entrenamiento RMSE 22.190229
Validación RMSE 7.963377
Test RMSE 9.257804
In [522]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_CRNN_11.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_CRNN_11 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_CRNN_11 = pd.concat([tabla_3_CRNN_11, pd.DataFrame([row])])

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3¶

In [523]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_CRNN_11, tabla_2_CRNN_11, tabla_3_CRNN_11], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 51.451080 Measurement + Hypo_Hyper Entrenamiento MAE 48.073353 Measurement + Hypo_Hyper Entrenamiento MAE 16.938581 Measurement + Hypo_Hyper
Validación MAE 64.229080 Measurement + Hypo_Hyper Validación MAE 60.276623 Measurement + Hypo_Hyper Validación MAE 6.353980 Measurement + Hypo_Hyper
Test MAE 68.542610 Measurement + Hypo_Hyper Test MAE 64.590080 Measurement + Hypo_Hyper Test MAE 7.541975 Measurement + Hypo_Hyper
Entrenamiento RMSE 56.974197 Measurement + Hypo_Hyper Entrenamiento RMSE 53.568554 Measurement + Hypo_Hyper Entrenamiento RMSE 22.190229 Measurement + Hypo_Hyper
Validación RMSE 64.944542 Measurement + Hypo_Hyper Validación RMSE 61.038773 Measurement + Hypo_Hyper Validación RMSE 7.963377 Measurement + Hypo_Hyper
Test RMSE 69.108482 Measurement + Hypo_Hyper Test RMSE 65.190681 Measurement + Hypo_Hyper Test RMSE 9.257804 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR CRNN_1, CRNN_2 y CRNN_3¶

In [524]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'CRNN'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_CRNN_11.loc[tabla_1_CRNN_11['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_CRNN_11.loc[tabla_1_CRNN_11['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_CRNN_11.loc[tabla_1_CRNN_11['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_CRNN_11.loc[tabla_2_CRNN_11['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_CRNN_11.loc[tabla_2_CRNN_11['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_CRNN_11.loc[tabla_2_CRNN_11['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_CRNN.loc[tabla_3_CRNN_11['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_CRNN.loc[tabla_3_CRNN_11['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_CRNN.loc[tabla_3_CRNN_11['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'CRNN': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO CRNN</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>CRNN_3 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
CRNN_11 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{CRNN_11}</div>'))

MEJOR RESULTADO CRNN

CRNN_3
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 9.861878 Measurement + Hypo_Hyper 3
Validación MAE 7.108992 Measurement + Hypo_Hyper 3
Test MAE 5.153802 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 14.029371 Measurement + Hypo_Hyper 3
Validación RMSE 8.081632 Measurement + Hypo_Hyper 3
Test RMSE 7.009937 Measurement + Hypo_Hyper 3

Conclusión CRNN¶

La Red Neuronal Recurrente Convolucional (CRNN) es una arquitectura de red neuronal que combina características de las redes neuronales convolucionales (CNN) y las redes neuronales recurrentes (RNN). Esta diseñada para modelar y aprender patrones en datos secuenciales, al tiempo que tiene en cuenta la estructura espacial de los datos, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

Al igual que las CNN, las CRNN utilizan capas convolucionales para extraer características locales de los datos de entrada. Estas capas convolucionales aplican filtros a ventanas de tamaño fijo, que se deslizan a lo largo de la secuencia para detectar patrones locales. Esto permite a la CRNN capturar características relevantes en diferentes partes de la secuencia de datos.

Sin embargo, a diferencia de las CNN tradicionales, las CRNN también incorporan la capacidad de modelar dependencias a largo plazo utilizando unidades recurrentes, como las LSTM. Las LSTM en la CRNN actúan como una capa de procesamiento secuencial adicional que se aplica después de las capas convolucionales. Estas unidades recurrentes permiten que la red mantenga una memoria de largo plazo y capture dependencias a largo plazo en la secuencia.

La combinación de capas convolucionales y unidades LSTM en una CRNN aprovecha tanto la capacidad de las CNN para extraer características locales como la capacidad de las RNN para modelar dependencias a largo plazo. Las capas convolucionales ayudan a capturar patrones locales en la secuencia, mientras que las unidades LSTM permiten que la red aprenda y recuerde dependencias a largo plazo entre los elementos de la secuencia.

En resumen, una CRNN es una arquitectura de red neuronal que combina capas convolucionales y unidades LSTM para modelar y aprender patrones en datos secuenciales. Las capas convolucionales extraen características locales de la secuencia, mientras que las unidades LSTM capturan dependencias a largo plazo. Esto permite a la CRNN capturar patrones complejos y modelar tanto la estructura espacial como las dependencias temporales en los datos secuenciales.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo CRNN es entrenado, se puede determinar que la arquitectura 3 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global arquitectura:

  • Mejor arquitectura 3:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor arquitectura es la 2 con las características de entrada Measurement + Hypo_Hyper.

Comparativa de resultados LSTM y CRNN¶

In [525]:
from IPython.display import display, HTML

html_tables_11 = f"""
<style>
    .main-container {{
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
    }}
    .table-wrapper {{
        display: inline-block;
        font-size: 14px;
        vertical-align: top;
        margin-right: 20px;
    }}
    .table-wrapper table {{
        font-size: 14px;
    }}
    .table-wrapper th, .table-wrapper td {{
        width: 100px;
        text-align: center;
    }}
    .table-wrapper h3 {{
        text-align: center;
        font-size: 18px;
        color: blue;
    }}
    h2 {{
        text-align: center;
    }}
</style>
<h2>Paciente 11</h2>
<div class="main-container">
    <div class="table-wrapper">
        <h3>LSTM</h3>
        {LSTM_11}
    </div>
    <div class="table-wrapper">
        <h3>CRNN</h3>
        {CRNN_11}
    </div>
</div>
"""


display(HTML(html_tables_11))

Paciente 11

LSTM

Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.665202 Measurement + Hypo_Hyper 2
Validación MAE 0.832716 Measurement + Hypo_Hyper 2
Test MAE 0.822650 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 5.385064 Measurement + Hypo_Hyper 2
Validación RMSE 1.542349 Measurement + Hypo_Hyper 2
Test RMSE 1.477171 Measurement + Hypo_Hyper 2

CRNN

Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 9.861878 Measurement + Hypo_Hyper 3
Validación MAE 7.108992 Measurement + Hypo_Hyper 3
Test MAE 5.153802 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 14.029371 Measurement + Hypo_Hyper 3
Validación RMSE 8.081632 Measurement + Hypo_Hyper 3
Test RMSE 7.009937 Measurement + Hypo_Hyper 3

Mejor Modelo (Paciente 11)¶

In [526]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'Arquitectura', 'Configuración Hiperparámetros'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Diccionarios para almacenar los nombres de las tablas
tables_arch1 = {'tabla_1_CRNN_11': 1, 'tabla_2_CRNN_11': 2, 'tabla_3_CRNN_11': 3}
tables_arch2 = {'tabla_1_LSTM_11': 1, 'tabla_2_LSTM_11': 2, 'tabla_3_LSTM_11': 3}

# Para cada fila
for row in rows:
    min_vals = []
    
    # Arquitectura 1
    for table_name, arch in tables_arch1.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'CRNN', arch))

    # Arquitectura 2
    for table_name, arch in tables_arch2.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'LSTM', arch))

    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val, min_col, min_arch, min_num = min(min_vals, key=lambda x: x[0])

    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')

    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'Arquitectura': [min_arch],
        'Configuración Hiperparámetros': [min_num]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px; font-weight:bold;'>MEJOR ARQUITECTURA (Paciente 11)</span></center>"
display(HTML(html_text))

# Aplica el estilo personalizado al DataFrame resultante
styled_result = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_result = styled_result.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Mostrar el DataFrame resultante con estilo y sin índices
final_output = styled_result.hide(axis='index').to_html()

# Utilizando la función `display(HTML())` para mostrar el contenido HTML en un div centrado en la página.
display(HTML("<div style='margin: 0 auto; width:70%'>" + final_output + "</div>"))

# Nombrando
styled_result3 = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])
styled_result3 = styled_result3.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Muestra la predicción del nivel de glucosa en los próximos 15 minutos
# Crea un HTML con el título y los datos

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_table = future_predictions_hypo_hype_2_LSTM_11.style.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

html = """
<div style='text-align: center;'>
<h2>Predicción del nivel de glucosa en los próximos 15 minutos</h2>
<div style='margin: 20px auto 0 40%; width:40%;'>
""" + styled_table.to_html() + "</div></div>"

# Muestra el HTML
display(HTML(html))

# Centrar la figura y la tabla utilizando CSS y HTML
display(HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
<h2>Precisión del modelo con Clarke Error Grid</h2>
"""))

# Mostrar la figura
display(fig_hypo_hype_2_LSTM_11)

# Centrar el contenido de la tabla utilizando CSS y ocultar los índices
styled_table = zone_df_hypo_hype_2_LSTM_11.style.set_properties(**{'text-align': 'center'}).hide(axis='index')

# Convertir la tabla en un objeto HTML
table_html = f"<center>{styled_table.to_html()}</center>"

# Mostrar la tabla centrada
display(HTML(table_html))

MEJOR ARQUITECTURA (Paciente 11)
Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.665202 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.832716 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.822650 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 5.385064 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.542349 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.477171 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-02-25 19:30:00 169.183929

Precisión del modelo con Clarke Error Grid

Zona Conteo Proporción
A 12989 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Conclusión Paciente 11¶

Los resultados nos aclara que la mejor opción para el paciente 11 es utilizar una arquitectura LSTM con una configuración de hiperparámetros 2.

Arquitectura LSTM con la LSTM_2 global:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

La conclusión final para el paciente 11 es utilizar la arquitectura LSTM con la configuración de hiperparámetros y las características de entrada Measurement + Hypo_Hyper para la predicción de sus niveles de glucosa para los próximos 15 minutos ya que nos arrojan unos resultados muy buenos.

Paciente 83
¶

La comparativa de soluciones con los modelos RNN y CRNN se va a realizar para el paciente 83 debido a que tras un análisis extenso se ha comprobado que es el 4º con mayor número de mediciones.

Se va a llevar a cabo la comparativa de soluciones con los modelos RNN y CRNN pero con la mejor solución ya obtenida con el paciente 22. De esta manera se va a trabajar con la mejor arquitectura de hiperparámetros y de características de entrada así se va a poder confirmar que el experimento se ha realizado con éxito.

La característica de entrada va a ser: 'Measurement + Hypo_Hyper'

In [239]:
# Filtrar los datos para el paciente 83
df_paciente_83 = df_top_5_pacientes[df_top_5_pacientes['Patient_ID'] == 83]

# Imprimir los resultados
df_paciente_83
Out[239]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-02-21 19:45:00 83 2018-02-21 1900-01-01 19:46:00 121 True False False 19 2
2018-02-21 20:00:00 83 2018-02-21 1900-01-01 20:01:00 124 True False False 20 2
2018-02-21 20:15:00 83 2018-02-21 1900-01-01 20:17:00 131 True False False 20 2
2018-02-21 20:30:00 83 2018-02-21 1900-01-01 20:32:00 128 True False False 20 2
2018-02-21 20:45:00 83 2018-02-21 1900-01-01 20:47:00 129 True False False 20 2
... ... ... ... ... ... ... ... ... ...
2022-03-14 06:45:00 83 2022-03-14 1900-01-01 06:45:00 159 True False False 6 0
2022-03-14 07:00:00 83 2022-03-14 1900-01-01 07:00:00 160 True False False 7 0
2022-03-14 07:15:00 83 2022-03-14 1900-01-01 07:15:00 164 True False False 7 0
2022-03-14 10:00:00 83 2022-03-14 1900-01-01 10:00:00 182 True False False 10 0
2022-03-14 10:45:00 83 2022-03-14 1900-01-01 10:45:00 168 True False False 10 0

9198 rows × 9 columns

Red Neuronal Recurrente (RNN) con el algoritmo Long Short-Term Memory (LSTM)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo LSTM con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Mesurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hyperglycemia" indica si una medición está por encima del rango normal.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [240]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_83[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [241]:
df
Out[241]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-02-21 19:46:00 121 False False
2018-02-21 20:01:00 124 False False
2018-02-21 20:17:00 131 False False
2018-02-21 20:32:00 128 False False
2018-02-21 20:47:00 129 False False
... ... ... ...
2022-03-14 06:45:00 159 False False
2022-03-14 07:00:00 160 False False
2022-03-14 07:15:00 164 False False
2022-03-14 10:00:00 182 False False
2022-03-14 10:45:00 168 False False

9198 rows × 3 columns

2. Dividir el conjunto de datos¶

In [242]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [243]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_33"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_41 (LSTM)              (None, 64)                17408     
                                                                 
 dense_33 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,473
Trainable params: 17,473
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [244]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1778/1778 [==============================] - 10s 5ms/step - loss: 2.5231 - val_loss: 0.4872
Epoch 2/20
1778/1778 [==============================] - 9s 5ms/step - loss: 1.7235 - val_loss: 0.7800
Epoch 3/20
1778/1778 [==============================] - 9s 5ms/step - loss: 2.1291 - val_loss: 1.8437
Epoch 4/20
1778/1778 [==============================] - 9s 5ms/step - loss: 1.5503 - val_loss: 0.8905

5. Evaluar¶

In [245]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_2_LSTM_83 = train_eval
val_eval_measurement_hypo_hype_2_LSTM_83 = val_eval
test_eval_measurement_hypo_hype_2_LSTM_83 = test_eval

train_rmse_measurement_hypo_hype_2_LSTM_83 = train_rmse
val_rmse_measurement_hypo_hype_2_LSTM_83 = val_rmse
test_rmse_measurement_hypo_hype_2_LSTM_83 = test_rmse
3556/3556 [==============================] - 5s 1ms/step
445/445 [==============================] - 1s 1ms/step
445/445 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 1.297979712486267
Evaluación Validación MAE: 0.8905425667762756
Evaluación Prueba MAE: 0.7647504210472107
Entrenamiento RMSE: 3.9664125442504883
Validación RMSE: 1.9132112264633179
Prueba RMSE: 1.4139435291290283

6. Gráfica¶

In [246]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [247]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')

# Almacena dato
future_predictions_hypo_hype_2_LSTM_83 = future_predictions
plt.show()
1/1 [==============================] - 0s 20ms/step

                     Datos predichos
date                                
2022-03-14 11:00:00       160.750839

6.2 Clarke Error Grid¶

In [248]:
def clarke_error_grid(ref_values, pred_values):
    assert (len(ref_values) == len(pred_values)), "Unequal number of values (reference: {}) (prediction: {}).".format(len(ref_values), len(pred_values))

    if max(ref_values) > 400 or max(pred_values) > 400:
        print("Input Warning: the maximum reference value {} or the maximum prediction value {} exceeds the normal physiological range of glucose (<400 mg/dl).".format(max(ref_values), max(pred_values)))
    if min(ref_values) < 0 or min(pred_values) < 0:
        print("Input Warning: the minimum reference value {} or the minimum prediction value {} is less than 0 mg/dl.".format(min(ref_values),  min(pred_values)))

    plt.clf()
    plt.scatter(ref_values, pred_values, marker='o', color='black', s=8)
    plt.title("Clarke Error Grid")
    plt.xlabel("Reference Concentration (mg/dl)")
    plt.ylabel("Prediction Concentration (mg/dl)")
    plt.xticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.yticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.gca().set_facecolor('white')
    plt.gca().set_xlim([0, 400])
    plt.gca().set_ylim([0, 400])
    plt.gca().set_aspect((400)/(400))
    plt.plot([0, 400], [0, 400], ':', c='black')
    plt.plot([0, 175/3], [70, 70], '-', c='black')
    plt.plot([175/3, 400/1.2], [70, 400], '-', c='black')
    plt.plot([70, 70], [84, 400],'-', c='black')
    plt.plot([0, 70], [180, 180], '-', c='black')
    plt.plot([70, 290], [180, 400],'-', c='black')
    plt.plot([70, 70], [0, 56], '-', c='black')
    plt.plot([70, 400], [56, 320],'-', c='black')
    plt.plot([180, 180], [0, 70], '-', c='black')
    plt.plot([180, 400], [70, 70], '-', c='black')
    plt.plot([240, 240], [70, 180],'-', c='black')
    plt.plot([240, 400], [180, 180], '-', c='black')
    plt.plot([130, 180], [0, 70], '-', c='black')
    plt.text(30, 15, "A", fontsize=15)
    plt.text(370, 260, "B", fontsize=15)
    plt.text(280, 370, "B", fontsize=15)
    plt.text(160, 370, "C", fontsize=15)
    plt.text(160, 15, "C", fontsize=15)
    plt.text(30, 140, "D", fontsize=15)
    plt.text(370, 120, "D", fontsize=15)
    plt.text(30, 370, "E", fontsize=15)
    plt.text(370, 15, "E", fontsize=15)

    zone = [0] * 5
    for i in range(len(ref_values)):
        if (ref_values[i] <= 70 and pred_values[i] <= 70) or (pred_values[i] <= 1.2 * ref_values[i] and pred_values[i] >= 0.8 * ref_values[i]):
            zone[0] += 1  # Zone A
        elif (ref_values[i] >= 180 and pred_values[i] <= 70) or (ref_values[i] <= 70 and pred_values[i] >= 180):
            zone[4] += 1  # Zone E
        elif ((ref_values[i] >= 70 and ref_values[i] <= 290) and pred_values[i] >= ref_values[i] + 110) or ((ref_values[i] >= 130 and ref_values[i] <= 180) and (pred_values[i] <= (7/5) * ref_values[i] - 182)):
            zone[2] += 1  # Zone C
        elif (ref_values[i] >= 240 and (pred_values[i] >= 70 and pred_values[i] <= 180)) or (ref_values[i] <= 175/3 and pred_values[i] <= 180 and pred_values[i] >= 70) or ((ref_values[i] >= 175/3 and ref_values[i] <= 70) and pred_values[i] >= (6/5) * ref_values[i]):
            zone[3] += 1  # Zone D
        else:
            zone[1] += 1  # Zone B

    return plt, zone

ref_values = y_test
pred_values = y_pred_test

plt, zone = clarke_error_grid(ref_values, pred_values)

# Asignar la figura a una variable antes de llamar a plt.show()
fig_hypo_hype_2_LSTM_83 = plt.gcf()

# Mostrar la figura
plt.show()

# Crear DataFrame para visualizar el conteo de zonas
zone_df = pd.DataFrame({'Zona': ['A', 'B', 'C', 'D', 'E'], 'Conteo': zone})

# Calcular la proporción de cada zona respecto al total
total = sum(zone)
zone_df['Proporción'] = zone_df['Conteo'] / total

# Mostrar la proporción en porcentaje
zone_df['Proporción'] = zone_df['Proporción'].apply(lambda x: '{:.2f}%'.format(x * 100))

# Asignar el DataFrame a una variable
zone_df_hypo_hype_2_LSTM_83 = zone_df

# Ocultar el índice y mostrar el DataFrame
print("Conteo de Zonas:")
display(zone_df_hypo_hype_2_LSTM_83.style.hide(axis="index"))
Conteo de Zonas:
Zona Conteo Proporción
A 14216 100.00%
B 0 0.00%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [249]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_83[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')


# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_1_LSTM_83 = train_eval
val_eval_measurement_hypo_hype_1_LSTM_83 = val_eval
test_eval_measurement_hypo_hype_1_LSTM_83 = test_eval

train_rmse_measurement_hypo_hype_1_LSTM_83 = train_rmse
val_rmse_measurement_hypo_hype_1_LSTM_83 = val_rmse
test_rmse_measurement_hypo_hype_1_LSTM_83 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_34"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_42 (LSTM)              (None, 32)                4608      
                                                                 
 dense_34 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,641
Trainable params: 4,641
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1778/1778 [==============================] - 7s 3ms/step - loss: 131.3375 - val_loss: 126.1949
Epoch 2/20
1778/1778 [==============================] - 6s 3ms/step - loss: 101.6932 - val_loss: 97.5350
Epoch 3/20
1778/1778 [==============================] - 6s 3ms/step - loss: 69.5751 - val_loss: 63.0589
Epoch 4/20
1778/1778 [==============================] - 6s 3ms/step - loss: 45.7497 - val_loss: 38.4023
Epoch 5/20
1778/1778 [==============================] - 6s 3ms/step - loss: 41.5749 - val_loss: 32.5733
Epoch 6/20
1778/1778 [==============================] - 6s 3ms/step - loss: 28.4565 - val_loss: 14.6228
Epoch 7/20
1778/1778 [==============================] - 5s 3ms/step - loss: 17.5932 - val_loss: 8.4972
Epoch 8/20
1778/1778 [==============================] - 6s 3ms/step - loss: 13.8982 - val_loss: 7.9877
Epoch 9/20
1778/1778 [==============================] - 6s 3ms/step - loss: 14.3223 - val_loss: 9.5809
Epoch 10/20
1778/1778 [==============================] - 6s 3ms/step - loss: 13.3829 - val_loss: 7.9308
Epoch 11/20
1778/1778 [==============================] - 6s 3ms/step - loss: 10.9583 - val_loss: 6.6837
Epoch 12/20
1778/1778 [==============================] - 6s 3ms/step - loss: 7.4704 - val_loss: 5.4437
Epoch 13/20
1778/1778 [==============================] - 6s 3ms/step - loss: 6.3315 - val_loss: 4.0603
Epoch 14/20
1778/1778 [==============================] - 6s 3ms/step - loss: 4.9322 - val_loss: 3.2371
Epoch 15/20
1778/1778 [==============================] - 6s 3ms/step - loss: 3.8945 - val_loss: 1.8845
Epoch 16/20
1778/1778 [==============================] - 6s 3ms/step - loss: 4.1503 - val_loss: 2.4562
Epoch 17/20
1778/1778 [==============================] - 5s 3ms/step - loss: 3.2180 - val_loss: 2.3931
Epoch 18/20
1778/1778 [==============================] - 5s 3ms/step - loss: 2.5665 - val_loss: 1.4322
Epoch 19/20
1778/1778 [==============================] - 6s 3ms/step - loss: 2.2854 - val_loss: 3.0481
Epoch 20/20
1778/1778 [==============================] - 6s 3ms/step - loss: 2.2029 - val_loss: 3.4068
3556/3556 [==============================] - 4s 1ms/step
445/445 [==============================] - 1s 1ms/step
445/445 [==============================] - 1s 1ms/step
Evaluación Entrenamiento MAE: 4.265793800354004
Evaluación Validación MAE: 3.4067575931549072
Evaluación Prueba MAE: 4.803277492523193
Entrenamiento RMSE: 6.575766563415527
Validación RMSE: 3.809464931488037
Prueba RMSE: 5.528017997741699

LSTM_3¶

In [250]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_83[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_LSTM_83 = train_eval
val_eval_measurement_hypo_hype_3_LSTM_83 = val_eval
test_eval_measurement_hypo_hype_3_LSTM_83 = test_eval

train_rmse_measurement_hypo_hype_3_LSTM_83 = train_rmse
val_rmse_measurement_hypo_hype_3_LSTM_83 = val_rmse
test_rmse_measurement_hypo_hype_3_LSTM_83 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_35"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_43 (LSTM)              (None, 8, 32)             4608      
                                                                 
 batch_normalization_22 (Bat  (None, 8, 32)            128       
 chNormalization)                                                
                                                                 
 dropout_22 (Dropout)        (None, 8, 32)             0         
                                                                 
 lstm_44 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_23 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_23 (Dropout)        (None, 64)                0         
                                                                 
 dense_35 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,889
Trainable params: 29,697
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1778/1778 [==============================] - 16s 8ms/step - loss: 36.8752 - val_loss: 8.1463
Epoch 2/20
1778/1778 [==============================] - 14s 8ms/step - loss: 20.8472 - val_loss: 7.5892
Epoch 3/20
1778/1778 [==============================] - 14s 8ms/step - loss: 20.2298 - val_loss: 7.7074
Epoch 4/20
1778/1778 [==============================] - 14s 8ms/step - loss: 19.9603 - val_loss: 5.2071
Epoch 5/20
1778/1778 [==============================] - 14s 8ms/step - loss: 19.6730 - val_loss: 4.5223
Epoch 6/20
1778/1778 [==============================] - 14s 8ms/step - loss: 19.2916 - val_loss: 1.9496
Epoch 7/20
1778/1778 [==============================] - 14s 8ms/step - loss: 19.0376 - val_loss: 6.2552
Epoch 8/20
1778/1778 [==============================] - 14s 8ms/step - loss: 18.8597 - val_loss: 3.3318
Epoch 9/20
1778/1778 [==============================] - 14s 8ms/step - loss: 18.8614 - val_loss: 3.5674
3556/3556 [==============================] - 8s 2ms/step
445/445 [==============================] - 1s 2ms/step
445/445 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 3.7792155742645264
Evaluación Validación MAE: 3.5674352645874023
Evaluación Prueba MAE: 3.7404351234436035
Entrenamiento RMSE: 5.072484970092773
Validación RMSE: 3.836060047149658
Prueba RMSE: 3.830667018890381

Resultado obtenido con 3 configuraciones de hiperparámetros LSTM¶

Después de realizar las pruebas se puede determinar que configuración de hiperparámetros del modelo LSTMy con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura LSTM, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura LSTM y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA LSTM_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [251]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_LSTM_83 vacía
tabla_1_LSTM_83 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_LSTM_83.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_LSTM_83.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_LSTM_83.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_LSTM_83.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_LSTM_83.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_LSTM_83.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_LSTM_83.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_LSTM_83
tabla_1_LSTM_83.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_LSTM_83
tabla_1_LSTM_83.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_LSTM_83

tabla_1_LSTM_83.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_LSTM_83
tabla_1_LSTM_83.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_LSTM_83
tabla_1_LSTM_83.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_LSTM_83

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_LSTM_83.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 4.265794
Validación MAE 3.406758
Test MAE 4.803277
Entrenamiento RMSE 6.575767
Validación RMSE 3.809465
Test RMSE 5.528018
In [252]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_LSTM_83.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_LSTM_83 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_LSTM_83 = pd.concat([tabla_1_LSTM_83, pd.DataFrame([row])])

TABLA LSTM_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [253]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla_2_LSTM_83 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_LSTM_83.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_LSTM_83.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_LSTM_83.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_LSTM_83.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_LSTM_83.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_LSTM_83.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_LSTM_83.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_LSTM_83
tabla_2_LSTM_83.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_LSTM_83
tabla_2_LSTM_83.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_LSTM_83

tabla_2_LSTM_83.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_LSTM_83
tabla_2_LSTM_83.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_LSTM_83
tabla_2_LSTM_83.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_LSTM_83

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_LSTM_83.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 1.297980
Validación MAE 0.890543
Test MAE 0.764750
Entrenamiento RMSE 3.966413
Validación RMSE 1.913211
Test RMSE 1.413944
In [254]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_LSTM_83.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_LSTM_83 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_LSTM_83 = pd.concat([tabla_2_LSTM_83, pd.DataFrame([row])])

TABLA LSTM_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • return_sequences: True.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5.
  • units: 64. Número de neuronas en la capa LSTM.
  • return_sequences: False. True/False. Determina si una capa LSTM devuelve la secuencia completa de salidas o solo la última; 'True' devuelve toda la secuencia, 'False' solo la última salida.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001). Aplica penalizaciones a los coeficientes de la capa durante el entrenamiento, en este caso utilizando la regularización L1 y L2, lo que puede ayudar a prevenir el sobreajuste.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [255]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_LSTM_83 vacía
tabla_3_LSTM_83 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_LSTM_83.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_LSTM_83.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_LSTM_83.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_LSTM_83.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_LSTM_83.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_LSTM_83.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_LSTM_83.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_LSTM_83
tabla_3_LSTM_83.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_LSTM_83
tabla_3_LSTM_83.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_LSTM_83

tabla_3_LSTM_83.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_LSTM_83
tabla_3_LSTM_83.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_LSTM_83
tabla_3_LSTM_83.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_LSTM_83

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_LSTM_83.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 3.779216
Validación MAE 3.567435
Test MAE 3.740435
Entrenamiento RMSE 5.072485
Validación RMSE 3.836060
Test RMSE 3.830667
In [256]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_LSTM_83.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_LSTM_83 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_LSTM_83 = pd.concat([tabla_3_LSTM_83, pd.DataFrame([row])])

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3¶

In [257]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_LSTM_83, tabla_2_LSTM_83, tabla_3_LSTM_83], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 4.265794 Measurement + Hypo_Hyper Entrenamiento MAE 1.297980 Measurement + Hypo_Hyper Entrenamiento MAE 3.779216 Measurement + Hypo_Hyper
Validación MAE 3.406758 Measurement + Hypo_Hyper Validación MAE 0.890543 Measurement + Hypo_Hyper Validación MAE 3.567435 Measurement + Hypo_Hyper
Test MAE 4.803277 Measurement + Hypo_Hyper Test MAE 0.764750 Measurement + Hypo_Hyper Test MAE 3.740435 Measurement + Hypo_Hyper
Entrenamiento RMSE 6.575767 Measurement + Hypo_Hyper Entrenamiento RMSE 3.966413 Measurement + Hypo_Hyper Entrenamiento RMSE 5.072485 Measurement + Hypo_Hyper
Validación RMSE 3.809465 Measurement + Hypo_Hyper Validación RMSE 1.913211 Measurement + Hypo_Hyper Validación RMSE 3.836060 Measurement + Hypo_Hyper
Test RMSE 5.528018 Measurement + Hypo_Hyper Test RMSE 1.413944 Measurement + Hypo_Hyper Test RMSE 3.830667 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR LSTM_1, LSTM_2 y LSTM_3¶

In [258]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'LSTM'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_LSTM_83.loc[tabla_1_LSTM_83['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_LSTM_83.loc[tabla_1_LSTM_83['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_LSTM_83.loc[tabla_1_LSTM_83['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_LSTM_83.loc[tabla_2_LSTM_83['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_LSTM_83.loc[tabla_2_LSTM_83['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_LSTM_83.loc[tabla_2_LSTM_83['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_LSTM_83.loc[tabla_3_LSTM_83['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_LSTM_83.loc[tabla_3_LSTM_83['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_LSTM_83.loc[tabla_3_LSTM_83['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'LSTM': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO LSTM</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>LSTM_2 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
LSTM_83 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{LSTM_83}</div>'))

MEJOR RESULTADO LSTM

LSTM_2
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.297980 Measurement + Hypo_Hyper 2
Validación MAE 0.890543 Measurement + Hypo_Hyper 2
Test MAE 0.764750 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 3.966413 Measurement + Hypo_Hyper 2
Validación RMSE 1.913211 Measurement + Hypo_Hyper 2
Test RMSE 1.413944 Measurement + Hypo_Hyper 2

Conclusión LSTM¶

La LSTM (Long Short-Term Memory) es un tipo de red neuronal recurrente diseñada para modelar y aprender patrones en secuencias de datos. Se utiliza mucho para tareas relacionadas con secuencias, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

La LSTM no sufre degradación del gradiente ni dificultades en la captura de dependencias a largo plazo debido a que utilizan una estructura interna de neuronas que les permite tener una memoria de información relevante a larga plazo y olvidar información obsoleta.

La LSTM tiene neuronas y cada una de las neuronas tiene tres puertas principales:

  1. Puerta de entrada (input gate): Determina qué nueva información debe agregarse al estado actual.
  2. Puerta de olvido (forget gate): Determina qué información antigua debe descartarse del estado actual.
  3. Puerta de salida (output gate): Determina qué parte del estado actual debe emitirse como salida. Además, las puertas contienen funciones de activación.

El diseño de las LSTMs permite que las unidades LSTM mantengan una memoria a largo plazo de la información relevante y eviten que la información se diluya a medida que se procesa a través de la secuencia. Esto las hace especialmente efectivas para modelar dependencias a largo plazo y capturar patrones complejos en datos secuenciales.

En resumen, una LSTM es una unidad recurrente que utiliza una estructura de neuronas con puertas de entrada, olvido y salida para modelar y aprender patrones en secuencias de datos. Su diseño les permite recordar información relevante a largo plazo y superar los problemas de degradación del gradiente, permitiendo un procesamiento efectivo de secuencias.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo LSTM es entrenado, se puede determinar que la LSTM_2 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global LSTM:

  • Mejor LSTM_2:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Evaluación Entrenamiento, Evaluación Validación, Evaluación Test, MSE Entrenamiento, MSE Validación y MSE Test estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor LSTM es la 2 con las características de entrada Measurement + Hypo_Hyper.

Redes Neuronales Recurrentes Convolucionales (CRNN)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo CRNN con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.¶

En este caso se esta entrenando al algoritmo CRNN con las variables 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal

Mejor arquitectura 3¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo CRNN:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [259]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_83[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [260]:
df
Out[260]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-02-21 19:46:00 121 False False
2018-02-21 20:01:00 124 False False
2018-02-21 20:17:00 131 False False
2018-02-21 20:32:00 128 False False
2018-02-21 20:47:00 129 False False
... ... ... ...
2022-03-14 06:45:00 159 False False
2022-03-14 07:00:00 160 False False
2022-03-14 07:15:00 164 False False
2022-03-14 10:00:00 182 False False
2022-03-14 10:45:00 168 False False

9198 rows × 3 columns

2. Dividir el conjunto de datos¶

In [261]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [262]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.5))
model.add(LSTM(64))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')
Model: "sequential_36"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv1d_9 (Conv1D)           (None, 6, 32)             320       
                                                                 
 batch_normalization_24 (Bat  (None, 6, 32)            128       
 chNormalization)                                                
                                                                 
 max_pooling1d_9 (MaxPooling  (None, 3, 32)            0         
 1D)                                                             
                                                                 
 dropout_24 (Dropout)        (None, 3, 32)             0         
                                                                 
 lstm_45 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_25 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_25 (Dropout)        (None, 64)                0         
                                                                 
 dense_36 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 25,601
Trainable params: 25,409
Non-trainable params: 192
_________________________________________________________________

4. Entrenar el modelo¶

In [263]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1778/1778 [==============================] - 8s 4ms/step - loss: 34.4055 - val_loss: 8.6330
Epoch 2/20
1778/1778 [==============================] - 6s 3ms/step - loss: 20.4260 - val_loss: 80.4782
Epoch 3/20
1778/1778 [==============================] - 6s 3ms/step - loss: 19.8140 - val_loss: 8.9892
Epoch 4/20
1778/1778 [==============================] - 6s 3ms/step - loss: 19.5680 - val_loss: 12.0031

5. Evaluar¶

In [264]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_CRNN_83 = train_eval
val_eval_measurement_hypo_hype_3_CRNN_83 = val_eval
test_eval_measurement_hypo_hype_3_CRNN_83 = test_eval

train_rmse_measurement_hypo_hype_3_CRNN_83 = train_rmse
val_rmse_measurement_hypo_hype_3_CRNN_83 = val_rmse
test_rmse_measurement_hypo_hype_3_CRNN_83 = test_rmse
3556/3556 [==============================] - 4s 1ms/step
445/445 [==============================] - 0s 1ms/step
445/445 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 15.653314590454102
Evaluación Validación MAE: 12.003067970275879
Evaluación Prueba MAE: 9.146519660949707
Entrenamiento RMSE: 18.360952377319336
Validación RMSE: 12.680938720703125
Prueba RMSE: 10.162103652954102

6. Gráfica¶

In [265]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [266]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 15ms/step

                     Datos predichos
date                                
2022-03-14 11:00:00       161.889572

Distintas arquitecturas CRNN probadas¶

CRNN_ 2¶

In [267]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_83[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(64, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_2_CRNN_83 = train_eval
val_eval_measurement_hypo_hype_2_CRNN_83 = val_eval
test_eval_measurement_hypo_hype_2_CRNN_83 = test_eval

train_rmse_measurement_hypo_hype_2_CRNN_83 = train_rmse
val_rmse_measurement_hypo_hype_2_CRNN_83 = val_rmse
test_rmse_measurement_hypo_hype_2_CRNN_83 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1778/1778 [==============================] - 6s 3ms/step - loss: 119.8786 - val_loss: 106.2520
Epoch 2/20
1778/1778 [==============================] - 4s 2ms/step - loss: 68.0027 - val_loss: 51.4258
Epoch 3/20
1778/1778 [==============================] - 4s 2ms/step - loss: 49.4637 - val_loss: 39.9957
Epoch 4/20
1778/1778 [==============================] - 4s 2ms/step - loss: 50.3305 - val_loss: 44.3829
Epoch 5/20
1778/1778 [==============================] - 4s 2ms/step - loss: 52.1855 - val_loss: 48.7529
Epoch 6/20
1778/1778 [==============================] - 4s 2ms/step - loss: 54.5695 - val_loss: 52.8490
3556/3556 [==============================] - 4s 914us/step
445/445 [==============================] - 0s 906us/step
445/445 [==============================] - 0s 912us/step
Evaluación Entrenamiento MAE: 45.16021728515625
Evaluación Validación MAE: 52.84911346435547
Evaluación Prueba MAE: 61.405765533447266
Entrenamiento RMSE: 49.626869201660156
Validación RMSE: 53.66616439819336
Prueba RMSE: 62.337501525878906

CRNN_1¶

In [268]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_83[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_1_CRNN_83 = train_eval
val_eval_measurement_hypo_hype_1_CRNN_83 = val_eval
test_eval_measurement_hypo_hype_1_CRNN_83 = test_eval

train_rmse_measurement_hypo_hype_1_CRNN_83 = train_rmse
val_rmse_measurement_hypo_hype_1_CRNN_83 = val_rmse
test_rmse_measurement_hypo_hype_1_CRNN_83 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1778/1778 [==============================] - 6s 2ms/step - loss: 121.3427 - val_loss: 108.6623
Epoch 2/20
1778/1778 [==============================] - 4s 2ms/step - loss: 76.3277 - val_loss: 64.2918
Epoch 3/20
1778/1778 [==============================] - 4s 2ms/step - loss: 50.5441 - val_loss: 41.4722
Epoch 4/20
1778/1778 [==============================] - 4s 2ms/step - loss: 52.7328 - val_loss: 51.4470
Epoch 5/20
1778/1778 [==============================] - 4s 2ms/step - loss: 47.5842 - val_loss: 46.6435
Epoch 6/20
1778/1778 [==============================] - 4s 2ms/step - loss: 52.6649 - val_loss: 53.7477
3556/3556 [==============================] - 4s 957us/step
445/445 [==============================] - 0s 1ms/step
445/445 [==============================] - 0s 964us/step
Evaluación Entrenamiento MAE: 45.90033721923828
Evaluación Validación MAE: 53.74784851074219
Evaluación Prueba MAE: 62.30449676513672
Entrenamiento RMSE: 50.40217590332031
Validación RMSE: 54.5512580871582
Prueba RMSE: 63.222808837890625

Resultado obtenido con 3 configuraciones de hiperparámetros CRNN¶

Después de realizar 3 pruebas se puede determinar que configuración de hiperparámetros del modelo CRNN y con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura CRNN, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas, siendo en este caso la Measurement + Hypoglycemia + Hyperglycemia.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura CRNN y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA CRNN_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [269]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_CRNN vacía
tabla_1_CRNN_83 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_CRNN_83.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_CRNN_83.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_CRNN_83.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_CRNN_83.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_CRNN_83.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_CRNN_83.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_CRNN_83.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_CRNN_83
tabla_1_CRNN_83.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_CRNN_83
tabla_1_CRNN_83.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_CRNN_83

tabla_1_CRNN_83.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_CRNN_83
tabla_1_CRNN_83.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_CRNN_83
tabla_1_CRNN_83.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_CRNN_83

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_CRNN_83.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 45.900337
Validación MAE 53.747849
Test MAE 62.304497
Entrenamiento RMSE 50.402176
Validación RMSE 54.551258
Test RMSE 63.222809
In [270]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_CRNN_83.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_CRNN_83 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_CRNN_83 = pd.concat([tabla_1_CRNN_83, pd.DataFrame([row])])

TABLA CRNN_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 64. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [271]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_2_CRNN vacía
tabla_2_CRNN_83 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_CRNN_83.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_CRNN_83.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_CRNN_83.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_CRNN_83.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_CRNN_83.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_CRNN_83.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_CRNN_83.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_CRNN_83
tabla_2_CRNN_83.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_CRNN_83
tabla_2_CRNN_83.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_CRNN_83

tabla_2_CRNN_83.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_CRNN_83
tabla_2_CRNN_83.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_CRNN_83
tabla_2_CRNN_83.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_CRNN_83

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_CRNN_83.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 45.160217
Validación MAE 52.849113
Test MAE 61.405766
Entrenamiento RMSE 49.626869
Validación RMSE 53.666164
Test RMSE 62.337502
In [272]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_CRNN_83.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_CRNN_83 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_CRNN_83 = pd.concat([tabla_2_CRNN_83, pd.DataFrame([row])])

TABLA CRNN_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [273]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_CRNN vacía
tabla_3_CRNN_83 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_CRNN_83.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_CRNN_83.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_CRNN_83.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_CRNN_83.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_CRNN_83.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_CRNN_83.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_CRNN_83.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_CRNN_83
tabla_3_CRNN_83.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_CRNN_83
tabla_3_CRNN_83.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_CRNN_83

tabla_3_CRNN_83.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_CRNN_83
tabla_3_CRNN_83.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_CRNN_83
tabla_3_CRNN_83.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_CRNN_83

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_CRNN_83.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 15.653315
Validación MAE 12.003068
Test MAE 9.146520
Entrenamiento RMSE 18.360952
Validación RMSE 12.680939
Test RMSE 10.162104
In [274]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_CRNN_83.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_CRNN_83 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_CRNN_83 = pd.concat([tabla_3_CRNN_83, pd.DataFrame([row])])

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3¶

In [275]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_CRNN_83, tabla_2_CRNN_83, tabla_3_CRNN_83], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 45.900337 Measurement + Hypo_Hyper Entrenamiento MAE 45.160217 Measurement + Hypo_Hyper Entrenamiento MAE 15.653315 Measurement + Hypo_Hyper
Validación MAE 53.747849 Measurement + Hypo_Hyper Validación MAE 52.849113 Measurement + Hypo_Hyper Validación MAE 12.003068 Measurement + Hypo_Hyper
Test MAE 62.304497 Measurement + Hypo_Hyper Test MAE 61.405766 Measurement + Hypo_Hyper Test MAE 9.146520 Measurement + Hypo_Hyper
Entrenamiento RMSE 50.402176 Measurement + Hypo_Hyper Entrenamiento RMSE 49.626869 Measurement + Hypo_Hyper Entrenamiento RMSE 18.360952 Measurement + Hypo_Hyper
Validación RMSE 54.551258 Measurement + Hypo_Hyper Validación RMSE 53.666164 Measurement + Hypo_Hyper Validación RMSE 12.680939 Measurement + Hypo_Hyper
Test RMSE 63.222809 Measurement + Hypo_Hyper Test RMSE 62.337502 Measurement + Hypo_Hyper Test RMSE 10.162104 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR CRNN_1, CRNN_2 y CRNN_3¶

In [276]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'CRNN'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_CRNN_83.loc[tabla_1_CRNN_83['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_CRNN_83.loc[tabla_1_CRNN_83['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_CRNN_83.loc[tabla_1_CRNN_83['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_CRNN_83.loc[tabla_2_CRNN_83['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_CRNN_83.loc[tabla_2_CRNN_83['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_CRNN_83.loc[tabla_2_CRNN_83['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_CRNN_83.loc[tabla_3_CRNN_83['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_CRNN_83.loc[tabla_3_CRNN_83['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_CRNN_83.loc[tabla_3_CRNN_83['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'CRNN': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO CRNN</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>CRNN_3 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
CRNN_83 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{CRNN_83}</div>'))

MEJOR RESULTADO CRNN

CRNN_3
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 15.653315 Measurement + Hypo_Hyper 3
Validación MAE 12.003068 Measurement + Hypo_Hyper 3
Test MAE 9.146520 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 18.360952 Measurement + Hypo_Hyper 3
Validación RMSE 12.680939 Measurement + Hypo_Hyper 3
Test RMSE 10.162104 Measurement + Hypo_Hyper 3

Conclusión CRNN¶

La Red Neuronal Recurrente Convolucional (CRNN) es una arquitectura de red neuronal que combina características de las redes neuronales convolucionales (CNN) y las redes neuronales recurrentes (RNN). Esta diseñada para modelar y aprender patrones en datos secuenciales, al tiempo que tiene en cuenta la estructura espacial de los datos, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

Al igual que las CNN, las CRNN utilizan capas convolucionales para extraer características locales de los datos de entrada. Estas capas convolucionales aplican filtros a ventanas de tamaño fijo, que se deslizan a lo largo de la secuencia para detectar patrones locales. Esto permite a la CRNN capturar características relevantes en diferentes partes de la secuencia de datos.

Sin embargo, a diferencia de las CNN tradicionales, las CRNN también incorporan la capacidad de modelar dependencias a largo plazo utilizando unidades recurrentes, como las LSTM. Las LSTM en la CRNN actúan como una capa de procesamiento secuencial adicional que se aplica después de las capas convolucionales. Estas unidades recurrentes permiten que la red mantenga una memoria de largo plazo y capture dependencias a largo plazo en la secuencia.

La combinación de capas convolucionales y unidades LSTM en una CRNN aprovecha tanto la capacidad de las CNN para extraer características locales como la capacidad de las RNN para modelar dependencias a largo plazo. Las capas convolucionales ayudan a capturar patrones locales en la secuencia, mientras que las unidades LSTM permiten que la red aprenda y recuerde dependencias a largo plazo entre los elementos de la secuencia.

En resumen, una CRNN es una arquitectura de red neuronal que combina capas convolucionales y unidades LSTM para modelar y aprender patrones en datos secuenciales. Las capas convolucionales extraen características locales de la secuencia, mientras que las unidades LSTM capturan dependencias a largo plazo. Esto permite a la CRNN capturar patrones complejos y modelar tanto la estructura espacial como las dependencias temporales en los datos secuenciales.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo CRNN es entrenado, se puede determinar que la CRNN_3 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global arquitectura:

  • Mejor CRNN 3:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor CRNN es la 3 con las características de entrada Measurement + Hypo_Hyper.

Comparativa de resultados LSTM y CRNN¶

In [277]:
from IPython.display import display, HTML

html_tables_83 = f"""
<style>
    .main-container {{
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
    }}
    .table-wrapper {{
        display: inline-block;
        font-size: 14px;
        vertical-align: top;
        margin-right: 20px;
    }}
    .table-wrapper table {{
        font-size: 14px;
    }}
    .table-wrapper th, .table-wrapper td {{
        width: 100px;
        text-align: center;
    }}
    .table-wrapper h3 {{
        text-align: center;
        font-size: 18px;
        color: blue;
    }}
    h2 {{
        text-align: center;
    }}
</style>
<h2>Paciente 83</h2>
<div class="main-container">
    <div class="table-wrapper">
        <h3>LSTM</h3>
        {LSTM_83}
    </div>
    <div class="table-wrapper">
        <h3>CRNN</h3>
        {CRNN_83}
    </div>
</div>
"""


display(HTML(html_tables_83))

Paciente 83

LSTM

Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 1.297980 Measurement + Hypo_Hyper 2
Validación MAE 0.890543 Measurement + Hypo_Hyper 2
Test MAE 0.764750 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 3.966413 Measurement + Hypo_Hyper 2
Validación RMSE 1.913211 Measurement + Hypo_Hyper 2
Test RMSE 1.413944 Measurement + Hypo_Hyper 2

CRNN

Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 15.653315 Measurement + Hypo_Hyper 3
Validación MAE 12.003068 Measurement + Hypo_Hyper 3
Test MAE 9.146520 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 18.360952 Measurement + Hypo_Hyper 3
Validación RMSE 12.680939 Measurement + Hypo_Hyper 3
Test RMSE 10.162104 Measurement + Hypo_Hyper 3

Mejor Modelo (Paciente 83)¶

In [278]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'Arquitectura', 'Configuración Hiperparámetros'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Diccionarios para almacenar los nombres de las tablas
tables_arch1 = {'tabla_1_CRNN_83': 1, 'tabla_2_CRNN_83': 2, 'tabla_3_CRNN_83': 3}
tables_arch2 = {'tabla_1_LSTM_83': 1, 'tabla_2_LSTM_83': 2, 'tabla_3_LSTM_83': 3}

# Para cada fila
for row in rows:
    min_vals = []
    
    # Arquitectura 1
    for table_name, arch in tables_arch1.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'CRNN', arch))

    # Arquitectura 2
    for table_name, arch in tables_arch2.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'LSTM', arch))

    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val, min_col, min_arch, min_num = min(min_vals, key=lambda x: x[0])

    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')

    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'Arquitectura': [min_arch],
        'Configuración Hiperparámetros': [min_num]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px; font-weight:bold;'>MEJOR ARQUITECTURA (Paciente 83)</span></center>"
display(HTML(html_text))

# Aplica el estilo personalizado al DataFrame resultante
styled_result = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_result = styled_result.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Mostrar el DataFrame resultante con estilo y sin índices
final_output = styled_result.hide(axis='index').to_html()

# Utilizando la función `display(HTML())` para mostrar el contenido HTML en un div centrado en la página.
display(HTML("<div style='margin: 0 auto; width:70%'>" + final_output + "</div>"))

# Nombrando
styled_result4 = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])
styled_result4 = styled_result4.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Muestra la predicción del nivel de glucosa en los próximos 15 minutos
# Crea un HTML con el título y los datos

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_table = future_predictions_hypo_hype_2_LSTM_83.style.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

html = """
<div style='text-align: center;'>
<h2>Predicción del nivel de glucosa en los próximos 15 minutos</h2>
<div style='margin: 20px auto 0 40%; width:40%;'>
""" + styled_table.to_html() + "</div></div>"

# Muestra el HTML
display(HTML(html))

# Centrar la figura y la tabla utilizando CSS y HTML
display(HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
<h2>Precisión del modelo con Clarke Error Grid</h2>
"""))


# Mostrar la figura
display(fig_hypo_hype_2_LSTM_83)

# Centrar el contenido de la tabla utilizando CSS y ocultar los índices
styled_table = zone_df_hypo_hype_2_LSTM_83.style.set_properties(**{'text-align': 'center'}).hide(axis='index')

# Convertir la tabla en un objeto HTML
table_html = f"<center>{styled_table.to_html()}</center>"

# Mostrar la tabla centrada
display(HTML(table_html))

MEJOR ARQUITECTURA (Paciente 83)
Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.297980 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.890543 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.764750 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 3.966413 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.913211 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.413944 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-14 11:00:00 160.750839

Precisión del modelo con Clarke Error Grid

Zona Conteo Proporción
A 14216 100.00%
B 0 0.00%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Conclusión Paciente 83¶

Los resultados nos aclara que la mejor opción para el paciente 83 es utilizar una arquitectura LSTM con una configuración de hiperparámetros 2.

Arquitectura LSTM con la LSTM_2 global:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

La conclusión final para el paciente 83 es utilizar la arquitectura LSTM con la configuración de hiperparámetros y las características de entrada Measurement + Hypo_Hyper para la predicción de sus niveles de glucosa para los próximos 15 minutos ya que nos arrojan unos resultados muy buenos.

Paciente 92
¶

La comparativa de soluciones con los modelos RNN y CRNN se va a realizar para el paciente 92 debido a que tras un análisis extenso se ha comprobado que es el 5º con mayor número de mediciones.

Se va a llevar a cabo la comparativa de soluciones con los modelos RNN y CRNN pero con la mejor solución ya obtenida con el paciente 22. De esta manera se va a trabajar con la mejor arquitectura de hiperparámetros y de características de entrada así se va a poder confirmar que el experimento se ha realizado con éxito.

La característica de entrada va a ser: 'Measurement + Hypo_Hyper'

In [279]:
# Filtrar los datos para el paciente 92
df_paciente_92 = df_top_5_pacientes[df_top_5_pacientes['Patient_ID'] == 92]

# Imprimir los resultados
df_paciente_92
Out[279]:
Patient_ID Measurement_date Measurement_time Measurement In_Range Hypoglycemia Hyperglycemia Hour_of_Day Day_of_Week
Datetime
2018-06-06 11:30:00 92 2018-06-06 1900-01-01 11:38:00 372 False False True 11 2
2018-06-06 11:45:00 92 2018-06-06 1900-01-01 11:53:00 365 False False True 11 2
2018-06-06 12:00:00 92 2018-06-06 1900-01-01 12:08:00 345 False False True 12 2
2018-06-06 12:15:00 92 2018-06-06 1900-01-01 12:23:00 327 False False True 12 2
2018-06-06 12:30:00 92 2018-06-06 1900-01-01 12:38:00 305 False False True 12 2
... ... ... ... ... ... ... ... ... ...
2022-01-10 12:00:00 92 2022-01-10 1900-01-01 12:00:00 177 True False False 12 0
2022-01-10 13:15:00 92 2022-01-10 1900-01-01 13:15:00 174 True False False 13 0
2022-01-10 15:00:00 92 2022-01-10 1900-01-01 15:00:00 186 True False False 15 0
2022-01-10 15:45:00 92 2022-01-10 1900-01-01 15:45:00 190 False False True 15 0
2022-01-10 16:00:00 92 2022-01-10 1900-01-01 16:00:00 193 False False True 16 0

8509 rows × 9 columns

Red Neuronal Recurrente (RNN) con el algoritmo Long Short-Term Memory (LSTM)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo LSTM con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'¶

En este caso se esta entrenando al algoritmo LSTM con las variables 'Mesurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal
  • "Hyperglycemia" indica si una medición está por encima del rango normal.

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo LSTM:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [280]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_92[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [281]:
df
Out[281]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-06-06 11:38:00 372 False True
2018-06-06 11:53:00 365 False True
2018-06-06 12:08:00 345 False True
2018-06-06 12:23:00 327 False True
2018-06-06 12:38:00 305 False True
... ... ... ...
2022-01-10 12:00:00 177 False False
2022-01-10 13:15:00 174 False False
2022-01-10 15:00:00 186 False False
2022-01-10 15:45:00 190 False True
2022-01-10 16:00:00 193 False True

8509 rows × 3 columns

2. Dividir el conjunto de datos¶

In [282]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [283]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(64, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1))  # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')
Model: "sequential_39"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_48 (LSTM)              (None, 64)                17408     
                                                                 
 dense_39 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 17,473
Trainable params: 17,473
Non-trainable params: 0
_________________________________________________________________

4. Entrenar el modelo¶

In [284]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1577/1577 [==============================] - 9s 5ms/step - loss: 4.8603 - val_loss: 0.7376
Epoch 2/20
1577/1577 [==============================] - 8s 5ms/step - loss: 2.5516 - val_loss: 0.2963
Epoch 3/20
1577/1577 [==============================] - 8s 5ms/step - loss: 2.1928 - val_loss: 0.8622
Epoch 4/20
1577/1577 [==============================] - 8s 5ms/step - loss: 2.1253 - val_loss: 0.9216
Epoch 5/20
1577/1577 [==============================] - 8s 5ms/step - loss: 1.8729 - val_loss: 1.6513

5. Evaluar¶

In [285]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_2_LSTM_92 = train_eval
val_eval_measurement_hypo_hype_2_LSTM_92 = val_eval
test_eval_measurement_hypo_hype_2_LSTM_92 = test_eval

train_rmse_measurement_hypo_hype_2_LSTM_92 = train_rmse
val_rmse_measurement_hypo_hype_2_LSTM_92 = val_rmse
test_rmse_measurement_hypo_hype_2_LSTM_92 = test_rmse
3154/3154 [==============================] - 5s 1ms/step
394/394 [==============================] - 1s 1ms/step
394/394 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 2.6005749702453613
Evaluación Validación MAE: 1.6512607336044312
Evaluación Prueba MAE: 1.7486741542816162
Entrenamiento RMSE: 6.021918773651123
Validación RMSE: 1.7085919380187988
Prueba RMSE: 2.037662982940674

6. Gráfica¶

In [286]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [287]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')

# Almacenar dato
future_predictions_hypo_hype_2_LSTM_92 = future_predictions

plt.show()
1/1 [==============================] - 0s 15ms/step

                     Datos predichos
date                                
2022-01-10 16:15:00       186.475082

6.2 Clarke Error Grid¶

In [288]:
def clarke_error_grid(ref_values, pred_values):
    assert (len(ref_values) == len(pred_values)), "Unequal number of values (reference: {}) (prediction: {}).".format(len(ref_values), len(pred_values))

    if max(ref_values) > 400 or max(pred_values) > 400:
        print("Input Warning: the maximum reference value {} or the maximum prediction value {} exceeds the normal physiological range of glucose (<400 mg/dl).".format(max(ref_values), max(pred_values)))
    if min(ref_values) < 0 or min(pred_values) < 0:
        print("Input Warning: the minimum reference value {} or the minimum prediction value {} is less than 0 mg/dl.".format(min(ref_values),  min(pred_values)))

    plt.clf()
    plt.scatter(ref_values, pred_values, marker='o', color='black', s=8)
    plt.title("Clarke Error Grid")
    plt.xlabel("Reference Concentration (mg/dl)")
    plt.ylabel("Prediction Concentration (mg/dl)")
    plt.xticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.yticks([0, 50, 100, 150, 200, 250, 300, 350, 400])
    plt.gca().set_facecolor('white')
    plt.gca().set_xlim([0, 400])
    plt.gca().set_ylim([0, 400])
    plt.gca().set_aspect((400)/(400))
    plt.plot([0, 400], [0, 400], ':', c='black')
    plt.plot([0, 175/3], [70, 70], '-', c='black')
    plt.plot([175/3, 400/1.2], [70, 400], '-', c='black')
    plt.plot([70, 70], [84, 400],'-', c='black')
    plt.plot([0, 70], [180, 180], '-', c='black')
    plt.plot([70, 290], [180, 400],'-', c='black')
    plt.plot([70, 70], [0, 56], '-', c='black')
    plt.plot([70, 400], [56, 320],'-', c='black')
    plt.plot([180, 180], [0, 70], '-', c='black')
    plt.plot([180, 400], [70, 70], '-', c='black')
    plt.plot([240, 240], [70, 180],'-', c='black')
    plt.plot([240, 400], [180, 180], '-', c='black')
    plt.plot([130, 180], [0, 70], '-', c='black')
    plt.text(30, 15, "A", fontsize=15)
    plt.text(370, 260, "B", fontsize=15)
    plt.text(280, 370, "B", fontsize=15)
    plt.text(160, 370, "C", fontsize=15)
    plt.text(160, 15, "C", fontsize=15)
    plt.text(30, 140, "D", fontsize=15)
    plt.text(370, 120, "D", fontsize=15)
    plt.text(30, 370, "E", fontsize=15)
    plt.text(370, 15, "E", fontsize=15)

    zone = [0] * 5
    for i in range(len(ref_values)):
        if (ref_values[i] <= 70 and pred_values[i] <= 70) or (pred_values[i] <= 1.2 * ref_values[i] and pred_values[i] >= 0.8 * ref_values[i]):
            zone[0] += 1  # Zone A
        elif (ref_values[i] >= 180 and pred_values[i] <= 70) or (ref_values[i] <= 70 and pred_values[i] >= 180):
            zone[4] += 1  # Zone E
        elif ((ref_values[i] >= 70 and ref_values[i] <= 290) and pred_values[i] >= ref_values[i] + 110) or ((ref_values[i] >= 130 and ref_values[i] <= 180) and (pred_values[i] <= (7/5) * ref_values[i] - 182)):
            zone[2] += 1  # Zone C
        elif (ref_values[i] >= 240 and (pred_values[i] >= 70 and pred_values[i] <= 180)) or (ref_values[i] <= 175/3 and pred_values[i] <= 180 and pred_values[i] >= 70) or ((ref_values[i] >= 175/3 and ref_values[i] <= 70) and pred_values[i] >= (6/5) * ref_values[i]):
            zone[3] += 1  # Zone D
        else:
            zone[1] += 1  # Zone B

    return plt, zone

ref_values = y_test
pred_values = y_pred_test

plt, zone = clarke_error_grid(ref_values, pred_values)

# Asignar la figura a una variable antes de llamar a plt.show()
fig_hypo_hype_2_LSTM_92 = plt.gcf()

# Mostrar la figura
plt.show()

# Crear DataFrame para visualizar el conteo de zonas
zone_df = pd.DataFrame({'Zona': ['A', 'B', 'C', 'D', 'E'], 'Conteo': zone})

# Calcular la proporción de cada zona respecto al total
total = sum(zone)
zone_df['Proporción'] = zone_df['Conteo'] / total

# Mostrar la proporción en porcentaje
zone_df['Proporción'] = zone_df['Proporción'].apply(lambda x: '{:.2f}%'.format(x * 100))

# Asignar el DataFrame a una variable
zone_df_hypo_hype_2_LSTM_92 = zone_df

# Ocultar el índice y mostrar el DataFrame
print("Conteo de Zonas:")
display(zone_df_hypo_hype_2_LSTM_92.style.hide(axis="index"))
Conteo de Zonas:
Zona Conteo Proporción
A 12607 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Distintas configuraciones de hiperparámetros probadas¶

LSTM_1¶

In [289]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_92[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')


# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_1_LSTM_92 = train_eval
val_eval_measurement_hypo_hype_1_LSTM_92 = val_eval
test_eval_measurement_hypo_hype_1_LSTM_92 = test_eval

train_rmse_measurement_hypo_hype_1_LSTM_92 = train_rmse
val_rmse_measurement_hypo_hype_1_LSTM_92 = val_rmse
test_rmse_measurement_hypo_hype_1_LSTM_92 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_40"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_49 (LSTM)              (None, 32)                4608      
                                                                 
 dense_40 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 4,641
Trainable params: 4,641
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
1577/1577 [==============================] - 7s 3ms/step - loss: 141.7285 - val_loss: 128.3646
Epoch 2/20
1577/1577 [==============================] - 5s 3ms/step - loss: 105.7363 - val_loss: 94.7597
Epoch 3/20
1577/1577 [==============================] - 5s 3ms/step - loss: 73.7526 - val_loss: 60.9658
Epoch 4/20
1577/1577 [==============================] - 5s 3ms/step - loss: 41.6170 - val_loss: 28.9085
Epoch 5/20
1577/1577 [==============================] - 5s 3ms/step - loss: 24.2091 - val_loss: 15.1024
Epoch 6/20
1577/1577 [==============================] - 5s 3ms/step - loss: 15.4074 - val_loss: 13.3994
Epoch 7/20
1577/1577 [==============================] - 5s 3ms/step - loss: 9.0328 - val_loss: 8.8385
Epoch 8/20
1577/1577 [==============================] - 5s 3ms/step - loss: 5.5254 - val_loss: 5.8917
Epoch 9/20
1577/1577 [==============================] - 5s 3ms/step - loss: 4.1879 - val_loss: 6.8421
Epoch 10/20
1577/1577 [==============================] - 5s 3ms/step - loss: 3.5972 - val_loss: 3.5702
Epoch 11/20
1577/1577 [==============================] - 5s 3ms/step - loss: 2.9827 - val_loss: 7.1057
Epoch 12/20
1577/1577 [==============================] - 5s 3ms/step - loss: 3.4827 - val_loss: 5.6182
Epoch 13/20
1577/1577 [==============================] - 5s 3ms/step - loss: 2.8942 - val_loss: 5.5391
3154/3154 [==============================] - 4s 1ms/step
394/394 [==============================] - 0s 1ms/step
394/394 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 10.129681587219238
Evaluación Validación MAE: 5.539064884185791
Evaluación Prueba MAE: 7.745981693267822
Entrenamiento RMSE: 16.782787322998047
Validación RMSE: 8.51518440246582
Prueba RMSE: 11.167258262634277

LSTM_3¶

In [290]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_92[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura LSTM
model = Sequential()
model.add(LSTM(32, return_sequences=True, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(LSTM(64, return_sequences=False, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001))) 
model.add(BatchNormalization())
model.add(Dropout(0.5))  # Aumentar la tasa de dropout
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_LSTM_92 = train_eval
val_eval_measurement_hypo_hype_3_LSTM_92 = val_eval
test_eval_measurement_hypo_hype_3_LSTM_92 = test_eval

train_rmse_measurement_hypo_hype_3_LSTM_92 = train_rmse
val_rmse_measurement_hypo_hype_3_LSTM_92 = val_rmse
test_rmse_measurement_hypo_hype_3_LSTM_92 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Model: "sequential_41"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_50 (LSTM)              (None, 8, 32)             4608      
                                                                 
 batch_normalization_26 (Bat  (None, 8, 32)            128       
 chNormalization)                                                
                                                                 
 dropout_26 (Dropout)        (None, 8, 32)             0         
                                                                 
 lstm_51 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_27 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_27 (Dropout)        (None, 64)                0         
                                                                 
 dense_41 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 29,889
Trainable params: 29,697
Non-trainable params: 192
_________________________________________________________________
Epoch 1/20
1577/1577 [==============================] - 15s 8ms/step - loss: 45.1828 - val_loss: 6.8346
Epoch 2/20
1577/1577 [==============================] - 13s 8ms/step - loss: 26.0557 - val_loss: 3.4417
Epoch 3/20
1577/1577 [==============================] - 12s 8ms/step - loss: 25.6547 - val_loss: 3.0756
Epoch 4/20
1577/1577 [==============================] - 12s 8ms/step - loss: 25.2847 - val_loss: 6.5311
Epoch 5/20
1577/1577 [==============================] - 12s 8ms/step - loss: 24.8147 - val_loss: 4.5255
Epoch 6/20
1577/1577 [==============================] - 12s 8ms/step - loss: 24.6303 - val_loss: 2.0810
Epoch 7/20
1577/1577 [==============================] - 12s 8ms/step - loss: 24.6045 - val_loss: 4.3893
Epoch 8/20
1577/1577 [==============================] - 12s 8ms/step - loss: 24.4178 - val_loss: 9.5948
Epoch 9/20
1577/1577 [==============================] - 12s 8ms/step - loss: 24.2508 - val_loss: 1.3785
Epoch 10/20
1577/1577 [==============================] - 12s 8ms/step - loss: 24.2612 - val_loss: 9.5452
Epoch 11/20
1577/1577 [==============================] - 12s 7ms/step - loss: 24.1985 - val_loss: 5.4882
Epoch 12/20
1577/1577 [==============================] - 12s 7ms/step - loss: 24.2996 - val_loss: 6.1648
3154/3154 [==============================] - 7s 2ms/step
394/394 [==============================] - 1s 2ms/step
394/394 [==============================] - 1s 2ms/step
Evaluación Entrenamiento MAE: 8.204286575317383
Evaluación Validación MAE: 6.164846420288086
Evaluación Prueba MAE: 7.067074775695801
Entrenamiento RMSE: 12.38003921508789
Validación RMSE: 6.820505619049072
Prueba RMSE: 7.639858722686768

Resultado obtenido con 3 configuraciones de hiperparámetros LSTM¶

Después de realizar las pruebas se puede determinar que configuración de hiperparámetros del modelo LSTMy con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura LSTM, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura LSTM y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA LSTM_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [291]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_LSTM_92 vacía
tabla_1_LSTM_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_LSTM_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_LSTM_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_LSTM_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_LSTM_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_LSTM_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_LSTM_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_LSTM_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_LSTM_92
tabla_1_LSTM_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_LSTM_92
tabla_1_LSTM_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_LSTM_92

tabla_1_LSTM_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_LSTM_92
tabla_1_LSTM_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_LSTM_92
tabla_1_LSTM_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_LSTM_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_LSTM_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_1
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 10.129682
Validación MAE 5.539065
Test MAE 7.745982
Entrenamiento RMSE 16.782787
Validación RMSE 8.515184
Test RMSE 11.167258
In [292]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_LSTM_92.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_LSTM_92 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_LSTM_92 = pd.concat([tabla_1_LSTM_92, pd.DataFrame([row])])

TABLA LSTM_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [293]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla vacía
tabla_2_LSTM_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_LSTM_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_LSTM_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_LSTM_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_LSTM_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_LSTM_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_LSTM_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_LSTM_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_LSTM_92
tabla_2_LSTM_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_LSTM_92
tabla_2_LSTM_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_LSTM_92

tabla_2_LSTM_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_LSTM_92
tabla_2_LSTM_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_LSTM_92
tabla_2_LSTM_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_LSTM_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_2</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_LSTM_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_2
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 2.600575
Validación MAE 1.651261
Test MAE 1.748674
Entrenamiento RMSE 6.021919
Validación RMSE 1.708592
Test RMSE 2.037663
In [294]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_LSTM_92.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_LSTM_92 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_LSTM_92 = pd.concat([tabla_2_LSTM_92, pd.DataFrame([row])])

TABLA LSTM_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 32. Número de neuronas en la capa LSTM.
  • return_sequences: True.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5.
  • units: 64. Número de neuronas en la capa LSTM.
  • return_sequences: False. True/False. Determina si una capa LSTM devuelve la secuencia completa de salidas o solo la última; 'True' devuelve toda la secuencia, 'False' solo la última salida.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • kernel_regularizer: L1L2 (0.001 y 0.001). Aplica penalizaciones a los coeficientes de la capa durante el entrenamiento, en este caso utilizando la regularización L1 y L2, lo que puede ayudar a prevenir el sobreajuste.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [295]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_LSTM_92 vacía
tabla_3_LSTM_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_LSTM_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_LSTM_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_LSTM_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_LSTM_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_LSTM_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_LSTM_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_LSTM_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_LSTM_92
tabla_3_LSTM_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_LSTM_92
tabla_3_LSTM_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_LSTM_92

tabla_3_LSTM_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_LSTM_92
tabla_3_LSTM_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_LSTM_92
tabla_3_LSTM_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_LSTM_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_LSTM_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA LSTM_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 8.204287
Validación MAE 6.164846
Test MAE 7.067075
Entrenamiento RMSE 12.380039
Validación RMSE 6.820506
Test RMSE 7.639859
In [296]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_LSTM_92.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_LSTM_92 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_LSTM_92 = pd.concat([tabla_3_LSTM_92, pd.DataFrame([row])])

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3¶

In [297]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_LSTM_92, tabla_2_LSTM_92, tabla_3_LSTM_92], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE LSTM_1, LSTM_2 y LSTM_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 10.129682 Measurement + Hypo_Hyper Entrenamiento MAE 2.600575 Measurement + Hypo_Hyper Entrenamiento MAE 8.204287 Measurement + Hypo_Hyper
Validación MAE 5.539065 Measurement + Hypo_Hyper Validación MAE 1.651261 Measurement + Hypo_Hyper Validación MAE 6.164846 Measurement + Hypo_Hyper
Test MAE 7.745982 Measurement + Hypo_Hyper Test MAE 1.748674 Measurement + Hypo_Hyper Test MAE 7.067075 Measurement + Hypo_Hyper
Entrenamiento RMSE 16.782787 Measurement + Hypo_Hyper Entrenamiento RMSE 6.021919 Measurement + Hypo_Hyper Entrenamiento RMSE 12.380039 Measurement + Hypo_Hyper
Validación RMSE 8.515184 Measurement + Hypo_Hyper Validación RMSE 1.708592 Measurement + Hypo_Hyper Validación RMSE 6.820506 Measurement + Hypo_Hyper
Test RMSE 11.167258 Measurement + Hypo_Hyper Test RMSE 2.037663 Measurement + Hypo_Hyper Test RMSE 7.639859 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR LSTM_1, LSTM_2 y LSTM_3¶

In [298]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'LSTM'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_LSTM_92.loc[tabla_1_LSTM_92['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_LSTM_92.loc[tabla_1_LSTM_92['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_LSTM_92.loc[tabla_1_LSTM_92['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_LSTM_92.loc[tabla_2_LSTM_92['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_LSTM_92.loc[tabla_2_LSTM_92['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_LSTM_92.loc[tabla_2_LSTM_92['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_LSTM_92.loc[tabla_3_LSTM_92['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_LSTM_92.loc[tabla_3_LSTM_92['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_LSTM_92.loc[tabla_3_LSTM_92['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'LSTM': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO LSTM</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>LSTM_2 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_result = result.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_result = styled_result.set_table_styles([
    {'selector': 'td:not(:first-child), th:not(:first-child)', 'props': [('text-align', 'center')]}
])
styled_result = styled_result.applymap(bold_rows, subset=['Evaluación+Métrica'])
LSTM_92 = styled_result.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{LSTM_92}</div>'))

MEJOR RESULTADO LSTM

LSTM_2
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 2.600575 Measurement + Hypo_Hyper 2
Validación MAE 1.651261 Measurement + Hypo_Hyper 2
Test MAE 1.748674 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 6.021919 Measurement + Hypo_Hyper 2
Validación RMSE 1.708592 Measurement + Hypo_Hyper 2
Test RMSE 2.037663 Measurement + Hypo_Hyper 2

Conclusión LSTM¶

La LSTM (Long Short-Term Memory) es un tipo de red neuronal recurrente diseñada para modelar y aprender patrones en secuencias de datos. Se utiliza mucho para tareas relacionadas con secuencias, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

La LSTM no sufre degradación del gradiente ni dificultades en la captura de dependencias a largo plazo debido a que utilizan una estructura interna de neuronas que les permite tener una memoria de información relevante a larga plazo y olvidar información obsoleta.

La LSTM tiene neuronas y cada una de las neuronas tiene tres puertas principales:

  1. Puerta de entrada (input gate): Determina qué nueva información debe agregarse al estado actual.
  2. Puerta de olvido (forget gate): Determina qué información antigua debe descartarse del estado actual.
  3. Puerta de salida (output gate): Determina qué parte del estado actual debe emitirse como salida. Además, las puertas contienen funciones de activación.

El diseño de las LSTMs permite que las unidades LSTM mantengan una memoria a largo plazo de la información relevante y eviten que la información se diluya a medida que se procesa a través de la secuencia. Esto las hace especialmente efectivas para modelar dependencias a largo plazo y capturar patrones complejos en datos secuenciales.

En resumen, una LSTM es una unidad recurrente que utiliza una estructura de neuronas con puertas de entrada, olvido y salida para modelar y aprender patrones en secuencias de datos. Su diseño les permite recordar información relevante a largo plazo y superar los problemas de degradación del gradiente, permitiendo un procesamiento efectivo de secuencias.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo LSTM es entrenado, se puede determinar que la LSTM_2 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global LSTM:

  • Mejor LSTM_2:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento y buena generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor LSTM es la 2 con LSTM con las características de entrada Measurement + Hypo_Hyper.

Redes Neuronales Recurrentes Convolucionales (CRNN)¶

A continuación se realiza los pasos correspondientes para su entrenamiento.

Algoritmo CRNN con características de entrada: 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.¶

En este caso se esta entrenando al algoritmo CRNN con las variables 'Measurement', 'Hypoglycemia' y 'Hyperglycemia'.

  • 'Mesurement' es el nivel de glucosa.
  • "Hyperglycemia" indica si una medición está por encima del rango normal.
  • "Hypoglycemia" indica si una medición está por debajo del rango normal

Mejor arquitectura 2¶

Explicación de cada uno de los hiperparámetros utilizados en el modelo CRNN:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 64. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

1. Preprocesamiento de los datos¶

In [299]:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.callbacks import EarlyStopping
from keras.regularizers import L1L2
from keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten

# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_92[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
In [300]:
df
Out[300]:
Measurement Hypoglycemia Hyperglycemia
Datetime
2018-06-06 11:38:00 372 False True
2018-06-06 11:53:00 365 False True
2018-06-06 12:08:00 345 False True
2018-06-06 12:23:00 327 False True
2018-06-06 12:38:00 305 False True
... ... ... ...
2022-01-10 12:00:00 177 False False
2022-01-10 13:15:00 174 False False
2022-01-10 15:00:00 186 False False
2022-01-10 15:45:00 190 False True
2022-01-10 16:00:00 193 False True

8509 rows × 3 columns

2. Dividir el conjunto de datos¶

In [301]:
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

3. Definir arquitectura del modelo¶

In [302]:
# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', kernel_regularizer=L1L2(l1=0.001, l2=0.001), input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(BatchNormalization())
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.5))
model.add(LSTM(64))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(1)) # 1 nodo de salida para Medición
model.summary()

# 17. Compilar el modelo
opt = SGD(learning_rate=0.001, momentum=0.9)
model.compile(optimizer=opt, loss='mean_absolute_error')
Model: "sequential_42"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv1d_12 (Conv1D)          (None, 6, 32)             320       
                                                                 
 batch_normalization_28 (Bat  (None, 6, 32)            128       
 chNormalization)                                                
                                                                 
 max_pooling1d_12 (MaxPoolin  (None, 3, 32)            0         
 g1D)                                                            
                                                                 
 dropout_28 (Dropout)        (None, 3, 32)             0         
                                                                 
 lstm_52 (LSTM)              (None, 64)                24832     
                                                                 
 batch_normalization_29 (Bat  (None, 64)               256       
 chNormalization)                                                
                                                                 
 dropout_29 (Dropout)        (None, 64)                0         
                                                                 
 dense_42 (Dense)            (None, 1)                 65        
                                                                 
=================================================================
Total params: 25,601
Trainable params: 25,409
Non-trainable params: 192
_________________________________________________________________

4. Entrenar el modelo¶

In [303]:
# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)
Epoch 1/20
1577/1577 [==============================] - 8s 4ms/step - loss: 44.7948 - val_loss: 14.8446
Epoch 2/20
1577/1577 [==============================] - 6s 4ms/step - loss: 25.5380 - val_loss: 23.8512
Epoch 3/20
1577/1577 [==============================] - 6s 4ms/step - loss: 25.2827 - val_loss: 22.1706
Epoch 4/20
1577/1577 [==============================] - 6s 4ms/step - loss: 24.9056 - val_loss: 16.5204

5. Evaluar¶

In [304]:
# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

# Almacenar datos
# Se almacenan los resultados de evaluación y la raíz del error cuadrático medio (RMSE) del modelo para su posterior uso en una tabla.
train_eval_measurement_hypo_hype_3_CRNN_92 = train_eval
val_eval_measurement_hypo_hype_3_CRNN_92 = val_eval
test_eval_measurement_hypo_hype_3_CRNN_92 = test_eval

train_rmse_measurement_hypo_hype_3_CRNN_92 = train_rmse
val_rmse_measurement_hypo_hype_3_CRNN_92 = val_rmse
test_rmse_measurement_hypo_hype_3_CRNN_92 = test_rmse
3154/3154 [==============================] - 4s 1ms/step
394/394 [==============================] - 0s 1ms/step
394/394 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 22.130481719970703
Evaluación Validación MAE: 16.520313262939453
Evaluación Prueba MAE: 9.488499641418457
Entrenamiento RMSE: 28.031646728515625
Validación RMSE: 24.606990814208984
Prueba RMSE: 15.674383163452148

6. Gráfica¶

In [305]:
# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()

6.1 Gráfica con datos originales¶

In [306]:
# 23. Crear una lista para almacenar fechas y datos reales y predichos
train_dates = train.index[seq_length+1:]
val_dates = val.index[seq_length+1:]
test_dates = test.index[seq_length+1:]

real_data = np.concatenate([y_train.ravel(), y_val.ravel(), y_test.ravel()])
predicted_data = np.concatenate([y_pred_train.ravel(), y_pred_val.ravel(), y_pred_test.ravel()])
dates = np.concatenate([train_dates,val_dates,test_dates])

# 24. Obtener los últimos datos conocidos para la predicción inicial (El for para hacer múltiples predicciones a intervalos de 15 minutos)
# Realizar predicciones para los próximos 15 minutos
predictions_15min = []
last_sequence = df[['Measurement', 'Hypoglycemia', 'Hyperglycemia']].values[-seq_length:].astype('float32')
for i in range(1):  # Rango 1 para predecir solo los próximos 15 minutos   
    next_prediction = model.predict(last_sequence.reshape(1, seq_length, 3))[0][0]
    predictions_15min.append(next_prediction)
    last_sequence = np.append(last_sequence[1:], [[next_prediction, last_sequence[-1][1], last_sequence[-1][2]]], axis=0) # Aquí también se añade la última característica

# Crear fechas para los próximos 15 minutos y el número de iteraciones "2" más una fila adicional para los datos conocidos
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date, periods=2, freq='15min')[1:]

# Agregar las predicciones futuras al DataFrame de resultados
future_predictions = pd.DataFrame({'date': future_dates, 'Datos predichos': predictions_15min})
future_predictions.set_index('date', inplace=True)

results = pd.DataFrame({'date': dates, 'Datos reales': real_data, 'Datos predichos': predicted_data})
results.set_index('date', inplace=True)
results = pd.concat([results, future_predictions])

# Graficar los resultados con las predicciones futuras
ax = results.plot(figsize=(14,7))
ax.scatter(results.index[-1:], results['Datos predichos'].iloc[-1:], color='r', s=5, zorder=5, label='Punto de predicción')  # Agrega un punto para resaltar la predicción
plt.ylabel('Medición')
plt.xlabel('Fecha')
plt.legend()

print(f'\n{future_predictions}')
plt.show()
1/1 [==============================] - 0s 15ms/step

                     Datos predichos
date                                
2022-01-10 16:15:00       178.019409

Distintas arquitecturas CRNN probadas¶

CRNN_ 2¶

In [307]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_92[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')

# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(64, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
model.compile(optimizer='adam', loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_2_CRNN_92 = train_eval
val_eval_measurement_hypo_hype_2_CRNN_92 = val_eval
test_eval_measurement_hypo_hype_2_CRNN_92 = test_eval

train_rmse_measurement_hypo_hype_2_CRNN_92 = train_rmse
val_rmse_measurement_hypo_hype_2_CRNN_92 = val_rmse
test_rmse_measurement_hypo_hype_2_CRNN_92 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1577/1577 [==============================] - 5s 3ms/step - loss: 137.6261 - val_loss: 121.8192
Epoch 2/20
1577/1577 [==============================] - 4s 2ms/step - loss: 95.6166 - val_loss: 80.7955
Epoch 3/20
1577/1577 [==============================] - 4s 2ms/step - loss: 78.9990 - val_loss: 71.4305
Epoch 4/20
1577/1577 [==============================] - 4s 2ms/step - loss: 62.0030 - val_loss: 55.3728
Epoch 5/20
1577/1577 [==============================] - 4s 2ms/step - loss: 60.8635 - val_loss: 56.0386
Epoch 6/20
1577/1577 [==============================] - 4s 2ms/step - loss: 59.4843 - val_loss: 51.9623
Epoch 7/20
1577/1577 [==============================] - 4s 2ms/step - loss: 60.2023 - val_loss: 55.2375
Epoch 8/20
1577/1577 [==============================] - 4s 2ms/step - loss: 84.6269 - val_loss: 79.4459
Epoch 9/20
1577/1577 [==============================] - 4s 2ms/step - loss: 60.3725 - val_loss: 55.1330
3154/3154 [==============================] - 3s 942us/step
394/394 [==============================] - 0s 950us/step
394/394 [==============================] - 0s 1ms/step
Evaluación Entrenamiento MAE: 52.09457015991211
Evaluación Validación MAE: 55.13300704956055
Evaluación Prueba MAE: 60.935394287109375
Entrenamiento RMSE: 60.18794631958008
Validación RMSE: 57.08860397338867
Prueba RMSE: 62.53763961791992

CRNN_1¶

In [308]:
# 1. Seleccionar columnas relevantes para la predicción
df = df_paciente_92[['Measurement_date', 'Measurement_time', 'Measurement', 'Hypoglycemia', 'Hyperglycemia']].copy()

# 2. Crear una columna de fecha y hora combinadas
df['Datetime'] = pd.to_datetime(df['Measurement_date'].dt.date.astype(str) + ' ' + df['Measurement_time'].dt.time.astype(str))

# 3. Establecer 'Datetime' como el índice
df.set_index('Datetime', inplace=True)

# 4. Descartar las columnas 'Measurement_date' y 'Measurement_time'
df = df.drop(columns=['Measurement_date', 'Measurement_time'])

# 5. Resamplear los datos en intervalos de 15 minutos
df_resampled = df.resample('15T').mean()

# 6. Ordenar el DataFrame por fecha antes de realizar el resampleo
df = df.sort_index()

# 7. Rellenar los valores nan con el último valor válido
def fill_nan(df):
    df_resampled = df.fillna(method='ffill')
    return df_resampled

# 8. Rellenar los valores faltantes
df_filled = fill_nan(df_resampled)

# 9. Verificar si hay algún valor NaN restante
assert not df_filled.isna().any().any(), "There are still NaN values in df_filled"

# 10. Convertir a float32
df_filled = df_filled.astype('float32')
# 11. Dividir el conjunto de datos en entrenamiento (80%), validación (10%) y prueba (10%) (shuffle=False)
train_size = int(len(df_filled) * 0.8)
val_size = int(len(df_filled) * 0.1)
test_size = len(df_filled) - train_size - val_size
train, val, test = df_filled.iloc[0:train_size], df_filled.iloc[train_size:train_size+val_size], df_filled.iloc[train_size+val_size:len(df_filled)]

# 12. Función create_sequences toma un DataFrame 'data' y una longitud de secuencia 'seq_length' como parámetros.
def create_sequences(data, seq_length):
    
    # Inicializamos dos listas vacías para almacenar las secuencias de entrada (xs) y las etiquetas de salida (ys).    
    xs = []
    ys = []
    
    # Iteramos a través de los índices del DataFrame 'data' hasta el índice que sea 'len(data) - seq_length - 1'.
    for i in range(len(data)-seq_length-1):
        x = data.iloc[i:(i+seq_length)]
        y = data.iloc[i+seq_length]['Measurement']
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

# Definir la longitud de la secuencia
seq_length = 8 # Son 2h -> 15 minutos 8 veces

# Crear secuencias para el entrenamiento, validación y prueba
X_train, y_train = create_sequences(train[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_val, y_val = create_sequences(val[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)
X_test, y_test = create_sequences(test[['Measurement', 'Hypoglycemia', 'Hyperglycemia']], seq_length)

# 13. Crear un callback de EarlyStopping para detener el entrenamiento cuando la pérdida de validación deja de mejorar
early_stop = EarlyStopping(monitor='val_loss', patience=3)

# 14. Reshape inputs for LSTM [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 3))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 3))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 3))

# 15. Tamaño del batch
batch_size = 64

# 16. Definir la arquitectura CRNN
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu', input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(32))
model.add(Flatten())
model.add(Dense(1))

# 17. Compilar el modelo
opt = Adam(learning_rate=0.001)
model.compile(optimizer=opt, loss='mean_absolute_error')

# 18. Entrenar el modelo
history = model.fit(
    X_train, y_train,
    batch_size=batch_size,
    epochs=20,
    validation_data=(X_val, y_val),
    callbacks=[early_stop],
    shuffle=False
)

# 19. Realizar predicciones
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
y_pred_test = model.predict(X_test)

# 20. Evaluar los conjuntos de entrenamiento, validación y prueba
train_eval = model.evaluate(X_train, y_train, verbose=0)
val_eval = model.evaluate(X_val, y_val, verbose=0)
test_eval = model.evaluate(X_test, y_test, verbose=0)
print(f'Evaluación Entrenamiento MAE: {train_eval}')
print(f'Evaluación Validación MAE: {val_eval}')
print(f'Evaluación Prueba MAE: {test_eval}')

# 21. Calcular la raíz del error cuadrático medio (RMSE) como métrica de evaluación
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
val_rmse = np.sqrt(mean_squared_error(y_val, y_pred_val))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
print(f'Entrenamiento RMSE: {train_rmse}')
print(f'Validación RMSE: {val_rmse}')
print(f'Prueba RMSE: {test_rmse}')

train_eval_measurement_hypo_hype_1_CRNN_92 = train_eval
val_eval_measurement_hypo_hype_1_CRNN_92 = val_eval
test_eval_measurement_hypo_hype_1_CRNN_92 = test_eval

train_rmse_measurement_hypo_hype_1_CRNN_92 = train_rmse
val_rmse_measurement_hypo_hype_1_CRNN_92 = val_rmse
test_rmse_measurement_hypo_hype_1_CRNN_92 = test_rmse

# 22. Graficar los resultados obtenidos en el entrenamiento y validación 
plt.plot(history.history['loss'], label='Pérdida de entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida de validación')
plt.legend()
plt.show()
Epoch 1/20
1577/1577 [==============================] - 5s 3ms/step - loss: 141.3239 - val_loss: 129.1220
Epoch 2/20
1577/1577 [==============================] - 4s 2ms/step - loss: 99.3118 - val_loss: 83.6327
Epoch 3/20
1577/1577 [==============================] - 4s 2ms/step - loss: 68.9105 - val_loss: 56.6121
Epoch 4/20
1577/1577 [==============================] - 4s 2ms/step - loss: 78.3871 - val_loss: 76.7176
Epoch 5/20
1577/1577 [==============================] - 4s 2ms/step - loss: 71.9943 - val_loss: 58.4343
Epoch 6/20
1577/1577 [==============================] - 4s 2ms/step - loss: 67.1119 - val_loss: 59.0008
3154/3154 [==============================] - 3s 888us/step
394/394 [==============================] - 0s 921us/step
394/394 [==============================] - 0s 957us/step
Evaluación Entrenamiento MAE: 55.50890350341797
Evaluación Validación MAE: 59.00083923339844
Evaluación Prueba MAE: 64.80318450927734
Entrenamiento RMSE: 63.38026809692383
Validación RMSE: 60.83229064941406
Prueba RMSE: 66.31214904785156

Resultado obtenido con 3 configuraciones de hiperparámetros CRNN¶

Después de realizar 3 pruebas se puede determinar que configuración de hiperparámetros del modelo CRNN y con que características de entrada ofrece mejores resultados.

Las características de entrada utilizadas son:

  • 'Measurement' + Hypoglycemia + Hyperglycemia

El hecho de hacer la prueba únicamente con Measurement + Hypoglycemia + Hyperglycemia se debe a que en general todas tienen resultados similares, pero esta en concreto en general ofrece mejores resultados.

Podemos ver que según la arquitectura CRNN, cada una de las arquitecturas tiene distintos hiperparámetros, ofrecen un rendimiento variado. Además, el resultado obtenido depende de las características de entrada utilizadas, siendo en este caso la Measurement + Hypoglycemia + Hyperglycemia.

Para simplificar la comparación y determinar fácilmente cuál arquitectura brinda el mejor rendimiento, se reune todos los resultados en una tabla. De esta manera, es posible identificar qué configuración de arquitectura CRNN y características de entrada logran un mejor desempeño en la predicción de los niveles de glucosa para los próximos 15 minutos.

TABLA CRNN_1¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [309]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_1_CRNN vacía
tabla_1_CRNN_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_1_CRNN_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_1_CRNN_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_1_CRNN_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_1_CRNN_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_1_CRNN_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_1_CRNN_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_1_CRNN_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_1_CRNN_92
tabla_1_CRNN_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_1_CRNN_92
tabla_1_CRNN_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_1_CRNN_92

tabla_1_CRNN_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_1_CRNN_92
tabla_1_CRNN_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_1_CRNN_92
tabla_1_CRNN_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_1_CRNN_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(2) {
        text-align: center;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_1</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_1_CRNN_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
{'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_1
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 10.129682 Measurement + Hypo_Hyper Entrenamiento MAE 2.600575 Measurement + Hypo_Hyper Entrenamiento MAE 8.204287 Measurement + Hypo_Hyper
Validación MAE 5.539065 Measurement + Hypo_Hyper Validación MAE 1.651261 Measurement + Hypo_Hyper Validación MAE 6.164846 Measurement + Hypo_Hyper
Test MAE 7.745982 Measurement + Hypo_Hyper Test MAE 1.748674 Measurement + Hypo_Hyper Test MAE 7.067075 Measurement + Hypo_Hyper
Entrenamiento RMSE 16.782787 Measurement + Hypo_Hyper Entrenamiento RMSE 6.021919 Measurement + Hypo_Hyper Entrenamiento RMSE 12.380039 Measurement + Hypo_Hyper
Validación RMSE 8.515184 Measurement + Hypo_Hyper Validación RMSE 1.708592 Measurement + Hypo_Hyper Validación RMSE 6.820506 Measurement + Hypo_Hyper
Test RMSE 11.167258 Measurement + Hypo_Hyper Test RMSE 2.037663 Measurement + Hypo_Hyper Test RMSE 7.639859 Measurement + Hypo_Hyper
In [310]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_1_CRNN_92.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_1_CRNN_92 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_1_CRNN_92 = pd.concat([tabla_1_CRNN_92, pd.DataFrame([row])])

TABLA CRNN_2¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo CRNN.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 64. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • units (LSTM): 32. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • Flatten: Una capa en la red neuronal que transforma una entrada multidimensional en una única dimensión.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [311]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_2_CRNN vacía
tabla_2_CRNN_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_2_CRNN_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_2_CRNN_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_2_CRNN_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_2_CRNN_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_2_CRNN_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_2_CRNN_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_2_CRNN_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_2_CRNN_92
tabla_2_CRNN_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_2_CRNN_92
tabla_2_CRNN_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_2_CRNN_92

tabla_2_CRNN_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_2_CRNN_92
tabla_2_CRNN_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_2_CRNN_92
tabla_2_CRNN_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_2_CRNN_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_2_CRNN_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 52.094570
Validación MAE 55.133007
Test MAE 60.935394
Entrenamiento RMSE 60.187946
Validación RMSE 57.088604
Test RMSE 62.537640
In [312]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_2_CRNN_92.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_2_CRNN_92 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_2_CRNN_92 = pd.concat([tabla_2_CRNN_92, pd.DataFrame([row])])

TABLA CRNN_3¶

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units (Conv1D): 32. Número de neuronas en la capa CRNN.
  • kernel_size: 3. Filtro de convolución de tamaño 3x3 a una secuencia de entrada para extraer características locales y construir representaciones de nivel superior a medida que se procesa la información a través de la red.
  • activation: 'relu'. Función de activación utilizada en la capa CRNN.
  • kernel_regularizer: L1L2 (0.001 y 0.001).
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • MaxPooling1D: Una capa de agrupación máxima unidimensional en una red neuronal convolucional.
  • pool_size: 2. Utilizará una ventana de tamaño 2 para realizar el proceso de agrupación.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • units (LSTM): 64. Número de neuronas en la capa LSTM. Cada unidad procesa la secuencia de entrada y captura información relevante a lo largo de ella.
  • BatchNormalization: Técnica de normalización que acelera el aprendizaje y proporciona cierta regularización, generalmente mejorando el rendimiento del modelo.
  • Dropout: 0.5. Método de regularización que aleatoriamente "desactiva" algunas neuronas en una capa durante cada iteración de entrenamiento, con un ratio de 0.5, para prevenir el sobreajuste.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'SGD'. Optimizador utilizado para
  • learning_rate: 0.001. Determina el tamaño de los pasos que se toman al ajustar los pesos del modelo durante el proceso de entrenamiento.
  • momentum: 0.9. Parámetro del optimizador SGD que acelera el aprendizaje en direcciones relevantes y amortigua las oscilaciones, ayudando a encontrar el mínimo global en funciones de pérdida que no son suaves.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.
In [313]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_CRNN vacía
tabla_3_CRNN_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_CRNN_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_CRNN_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_CRNN_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_CRNN_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_CRNN_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_CRNN_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_CRNN_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_CRNN_92

tabla_3_CRNN_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_CRNN_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_CRNN_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 22.130482
Validación MAE 16.520313
Test MAE 9.488500
Entrenamiento RMSE 28.031647
Validación RMSE 24.606991
Test RMSE 15.674383
In [314]:
import pandas as pd
from IPython.display import display, HTML

# Crear la tabla_3_CRNN vacía
tabla_3_CRNN_92 = pd.DataFrame(columns=[
    'Evaluación+Métrica',
    'Measurement + Hypo_Hyper',
])

# Definir los valores en cada posición
tabla_3_CRNN_92.loc[1, 'Evaluación+Métrica'] = '<b>Entrenamiento MAE</b>'
tabla_3_CRNN_92.loc[2, 'Evaluación+Métrica'] = '<b>Validación MAE</b>'
tabla_3_CRNN_92.loc[3, 'Evaluación+Métrica'] = '<b>Test MAE</b>'

tabla_3_CRNN_92.loc[4, 'Evaluación+Métrica'] = '<b>Entrenamiento RMSE</b>'
tabla_3_CRNN_92.loc[5, 'Evaluación+Métrica'] = '<b>Validación RMSE</b>'
tabla_3_CRNN_92.loc[6, 'Evaluación+Métrica'] = '<b>Test RMSE</b>'

tabla_3_CRNN_92.loc[1, 'Measurement + Hypo_Hyper'] = train_eval_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[2, 'Measurement + Hypo_Hyper'] = val_eval_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[3, 'Measurement + Hypo_Hyper'] = test_eval_measurement_hypo_hype_3_CRNN_92

tabla_3_CRNN_92.loc[4, 'Measurement + Hypo_Hyper'] = train_rmse_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[5, 'Measurement + Hypo_Hyper'] = val_rmse_measurement_hypo_hype_3_CRNN_92
tabla_3_CRNN_92.loc[6, 'Measurement + Hypo_Hyper'] = test_rmse_measurement_hypo_hype_3_CRNN_92

# Usa CSS para definir el ancho de la columna Evaluación+Métrica
css = """
<style>
    .dataframe td:nth-child(1) {
        width: 165px;
    }
</style>
"""
# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>TABLA CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = tabla_3_CRNN_92.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

TABLA CRNN_3
Evaluación+Métrica Measurement + Hypo_Hyper
Entrenamiento MAE 22.130482
Validación MAE 16.520313
Test MAE 9.488500
Entrenamiento RMSE 28.031647
Validación RMSE 24.606991
Test RMSE 15.674383
In [315]:
# Crear una copia de la tabla original y convertir las columnas a tipo numérico
tabla_copia = tabla_3_CRNN_92.copy()
cols_to_convert = ['Measurement + Hypo_Hyper']

for col in cols_to_convert:
    tabla_copia[col] = pd.to_numeric(tabla_copia[col], errors='coerce')

# Crear una nueva tabla vacía
tabla_3_CRNN_92 = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada'])

# Obtener los valores mínimos y la columna correspondiente de cada fila en la copia
for i in range(1, 7):
    min_val = tabla_copia.loc[i, 'Measurement':].min()
    min_col = tabla_copia.loc[i, 'Measurement':].astype(float).idxmin()
    row = {
        'Evaluación+Métrica': tabla_copia.loc[i, 'Evaluación+Métrica'],
        'Valor mínimo': min_val,
        'Características de entrada': min_col
    }
    tabla_3_CRNN_92 = pd.concat([tabla_3_CRNN_92, pd.DataFrame([row])])

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3¶

In [316]:
# Combina las tablas horizontalmente
combined_table = pd.concat([tabla_1_CRNN_92, tabla_2_CRNN_92, tabla_3_CRNN_92], axis=1)

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = combined_table.style.set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:not(:first-child):not(:nth-child(4)):not(:nth-child(7))', 'props': [('text-align', 'center')]},
    {'selector': 'th', 'props': [('text-align', 'center')]}
])
table_html = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{table_html}</div>'))

COMPARATIVA ENTRE CRNN_1, CRNN_2 y CRNN_3
Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada Evaluación+Métrica Valor mínimo Características de entrada
Entrenamiento MAE 55.508904 Measurement + Hypo_Hyper Entrenamiento MAE 52.094570 Measurement + Hypo_Hyper Entrenamiento MAE 22.130482 Measurement + Hypo_Hyper
Validación MAE 59.000839 Measurement + Hypo_Hyper Validación MAE 55.133007 Measurement + Hypo_Hyper Validación MAE 16.520313 Measurement + Hypo_Hyper
Test MAE 64.803185 Measurement + Hypo_Hyper Test MAE 60.935394 Measurement + Hypo_Hyper Test MAE 9.488500 Measurement + Hypo_Hyper
Entrenamiento RMSE 63.380268 Measurement + Hypo_Hyper Entrenamiento RMSE 60.187946 Measurement + Hypo_Hyper Entrenamiento RMSE 28.031647 Measurement + Hypo_Hyper
Validación RMSE 60.832291 Measurement + Hypo_Hyper Validación RMSE 57.088604 Measurement + Hypo_Hyper Validación RMSE 24.606991 Measurement + Hypo_Hyper
Test RMSE 66.312149 Measurement + Hypo_Hyper Test RMSE 62.537640 Measurement + Hypo_Hyper Test RMSE 15.674383 Measurement + Hypo_Hyper

RESULTADO DE LA COMPARATIVA A MEJOR CRNN_1, CRNN_2 y CRNN_3¶

In [317]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'CRNN'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Para cada fila
for row in rows:
    # Encontrar el valor mínimo y la columna correspondiente para cada arquitectura
    min_val_1 = tabla_1_CRNN_92.loc[tabla_1_CRNN_92['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_1_CRNN_92.loc[tabla_1_CRNN_92['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_1 = tabla_1_CRNN_92.loc[tabla_1_CRNN_92['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_1 = None
    
    min_val_2 = tabla_2_CRNN_92.loc[tabla_2_CRNN_92['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_2_CRNN_92.loc[tabla_2_CRNN_92['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_2 = tabla_2_CRNN_92.loc[tabla_2_CRNN_92['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_2 = None
    
    min_val_3 = tabla_3_CRNN_92.loc[tabla_3_CRNN_92['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
    if not tabla_3_CRNN_92.loc[tabla_3_CRNN_92['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
        min_col_3 = tabla_3_CRNN_92.loc[tabla_3_CRNN_92['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
    else:
        min_col_3 = None
    
    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val = min(min_val_1, min_val_2, min_val_3)
    if min_val == min_val_1:
        min_col = min_col_1
        architecture = 1
    elif min_val == min_val_2:
        min_col = min_col_2
        architecture = 2
    else:
        min_col = min_col_3
        architecture = 3
    
    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')
    
    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'CRNN': [architecture]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>MEJOR RESULTADO CRNN</span></center>"
display(HTML(html_text))

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px;'>CRNN_3 <br>(Measurement + Hypo_Hyper)</span></center>"
display(HTML(html_text))

# Aplicar el estilo personalizado al DataFrame
styled_table = result.style.applymap(bold_rows, subset=['Evaluación+Métrica']).set_table_attributes("style='margin-left: auto; margin-right: auto;'")
styled_table = styled_table.set_table_styles([
    {'selector': 'td:nth-child(4)', 'props': [('text-align', 'center')]},
    {'selector': 'td:nth-child(2)', 'props': [('text-align', 'center')]}
])

# Mostrar el DataFrame resultante con estilo y sin índices
CRNN_92 = styled_table.hide(axis='index').to_html()

# Mostrar la tabla centrada
display(HTML(f'<div style="display: flex; justify-content: center;">{CRNN_92}</div>'))

MEJOR RESULTADO CRNN

CRNN_3
(Measurement + Hypo_Hyper)
Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 22.130482 Measurement + Hypo_Hyper 3
Validación MAE 16.520313 Measurement + Hypo_Hyper 3
Test MAE 9.488500 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 28.031647 Measurement + Hypo_Hyper 3
Validación RMSE 24.606991 Measurement + Hypo_Hyper 3
Test RMSE 15.674383 Measurement + Hypo_Hyper 3

Conclusión CRNN¶

La Red Neuronal Recurrente Convolucional (CRNN) es una arquitectura de red neuronal que combina características de las redes neuronales convolucionales (CNN) y las redes neuronales recurrentes (RNN). Esta diseñada para modelar y aprender patrones en datos secuenciales, al tiempo que tiene en cuenta la estructura espacial de los datos, siendo en este caso una buena elección dada la serie temporal que se utiliza para la predicción de los niveles de glucosa.

Al igual que las CNN, las CRNN utilizan capas convolucionales para extraer características locales de los datos de entrada. Estas capas convolucionales aplican filtros a ventanas de tamaño fijo, que se deslizan a lo largo de la secuencia para detectar patrones locales. Esto permite a la CRNN capturar características relevantes en diferentes partes de la secuencia de datos.

Sin embargo, a diferencia de las CNN tradicionales, las CRNN también incorporan la capacidad de modelar dependencias a largo plazo utilizando unidades recurrentes, como las LSTM. Las LSTM en la CRNN actúan como una capa de procesamiento secuencial adicional que se aplica después de las capas convolucionales. Estas unidades recurrentes permiten que la red mantenga una memoria de largo plazo y capture dependencias a largo plazo en la secuencia.

La combinación de capas convolucionales y unidades LSTM en una CRNN aprovecha tanto la capacidad de las CNN para extraer características locales como la capacidad de las RNN para modelar dependencias a largo plazo. Las capas convolucionales ayudan a capturar patrones locales en la secuencia, mientras que las unidades LSTM permiten que la red aprenda y recuerde dependencias a largo plazo entre los elementos de la secuencia.

En resumen, una CRNN es una arquitectura de red neuronal que combina capas convolucionales y unidades LSTM para modelar y aprender patrones en datos secuenciales. Las capas convolucionales extraen características locales de la secuencia, mientras que las unidades LSTM capturan dependencias a largo plazo. Esto permite a la CRNN capturar patrones complejos y modelar tanto la estructura espacial como las dependencias temporales en los datos secuenciales.

Las fases de evaluación MAE y métrica RMSE utilizadas son:

  • Evaluación de Entrenamiento MAE y Entrenamiento RMSE: Durante el entrenamiento de un modelo de aprendizaje automático, se busca aprender de los datos de entrada y ajustar los parámetros del modelo para minimizar el error de predicción. La Evaluación Entrenamiento MAE y las métricas Raíz del Error Cuadrático Medio (RMSE) de entrenamiento son medidas que nos indican qué tan bien está aprendiendo el modelo de los datos de entrenamiento.
    • Entrenamiento MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de MAE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
    • Entrenamiento RMSE: es una métrica que calcula la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de entrenamiento. Un valor bajo de RMSE indica que las predicciones del modelo están muy cerca de los valores reales, lo que significa que el modelo está aprendiendo adecuadamente de los datos de entrenamiento.
  • Evaluación de Validación MAE y Validación RMSE: Durante la fase de validación, el modelo se prueba con un conjunto de datos diferente al de entrenamiento. Esto nos ayuda a ajustar los hiperparámetros del modelo y evitar el sobreajuste.
    • Validación MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Esta midiendo qué tan bien el modelo se ajusta a los datos de validación. Al igual que en la fase de entrenamiento, un valor bajo indica un buen ajuste del modelo a los datos de validación. El MAE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
    • Validación RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de validación. Un RMSE bajo indica que el modelo puede generalizar bien a nuevos datos, lo cual es importante para un buen modelo de aprendizaje automático.
  • Evaluación de Test MAE y Test RMSE: Esta es la etapa final en la que se prueba el modelo con un conjunto de datos de prueba que no ha sido visto durante las fases de entrenamiento y validación.
    • Test MAE: es una métrica que calcula el promedio del valor absoluto de los errores entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Esta midiendo qué tan bien el modelo se ajusta a los datos de prueba. El Test MAE bajo indica un mejor rendimiento del modelo en datos que no ha visto previamente.
    • Test RMSE: es la raíz cuadrada del promedio de los errores al cuadrado entre las predicciones del modelo y los valores reales en el conjunto de datos de prueba. Un Test RMSE bajo indica que el modelo ha aprendido correctamente y puede generalizar bien a nuevos datos, lo cual es crítico para un buen modelo de aprendizaje automático.

Análisis de los resultados:

En el análisis de los resultados hay que tener en cuenta que al ser un algoritmo de aprendizaje automático se va a obtener distintos resultados en cada entrenamiento.

Las razones de los resultados distintos son varias y son las siguientes:

  • Inicialización aleatoria de los parámetros: Las redes neuronales utilizan inicialización aleatoria de los pesos y sesgos iniciales. Debido a esta aleatoriedad, el modelo comienza en diferentes estados y puede converger a óptimos locales diferentes en cada ejecución.
  • Muestreo aleatorio de datos: El orden en el que se presentan los datos de entrenamiento al algoritmo puede afectar el resultado final.
  • Regularización y técnicas de regularización estocástica: El uso de regularización como L1 o L2 para controlar la complejidad del modelo y evitar sobreajuste. Esta regularización introduce términos adicionales en la función de perdida (loss), pudiendo afectar el resultado final.
  • Hiperparámetros: La configuración previa al entrenamiento del modelo lo cual no se aprende del conjunto de datos, como la tasa de aprendizaje (learning_rate), tamaño del lote, número de capas ocultas, etc. Estos hiperparámetros pueden influir en el resultado final.
  • Sensibilidad a condiciones iniciales y ruido: Los optimizadores pueden ser sensibles a las condiciones finales y al ruido de los datos. Las variaciones que puedan haber en los datos de entrenamiento o en los valores iniciales pueden influir en el resutlado final.

En resumen, hay múltiples factores que contribuyen a que cada ejecución de entrenamiento produzca resultados diferentes. La aleatoriedad en la inicialización, el muestreo de datos, las técnicas de regularización, los hiperparámetros y la sensibilidad a las condiciones iniciales y al ruido son algunos de los factores más comunes que generan variabilidad en los resultados de loss.

En el análisis de los resultados se puede observar que los resultados obtenidos son bastante similares, con valores bajos, tanto para la Evaluación MAE como para la métrica RMSE.

A pesar de que hay pequeñas variaciones en los resultados cada vez que el modelo CRNN es entrenado, se puede determinar que la arquitectura 3 obtiene mejores resultados con las características de entrada Measurement + Hypo_Hyper.

En un análisis global arquitectura:

  • Mejor arquitectura 3:
    • Entrenamiento MAE: Obtiene mejores resultados, alto ajuste a los datos de entrenamiento dado al valor muy bajo obtenido. Resulta con alta capacidad de realizar predicciones precisas sobre los datos.
    • Entrenamiento RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje en el entrenamiento.
    • Validación MAE: Obtiene mejores resultados, alto ajuste a los datos de validación dado al valor muy bajo obtenido.
    • Validación RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales del conjunto de datos de validación. Resulta con alta generalización a nuevos datos.
    • Test MAE: Obtiene mejores resultados, alto ajuste a los datos de prueba dado al valor muy bajo obtenido. Resulta con alto rendimiento para datos todavía no vistos.
    • Test RMSE: Obtiene mejores resultados, alto promedio en los errores al cuadrado entre las predicciones del modelo y los valores reales. Resulta con alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante.

En un análisis global características de entrada:

  • Measurement + Hypo_Hyper: En general tiene mejor resultado en Entrenamiento MAE, Validación MAE, Test MAE, Entrenamiento RMSE, Validación RMSE y Test RMSE, estas evaluaciones determinan que es capaz de realizar un alto entrenamiento, alto aprendizaje y alta generalización a nuevos datos, lo cual es muy importante para la predicción de los niveles de glucosa y para predecir los próximos 15 minutos.

La conclusión a la que se ha llegado tras las distintas evaluaciones y comparativas se ha determinado que la mejor arquitectura es la 2 con LSTM con las características de entrada Measurement + Hypo_Hyper.

Comparativa de resultados LSTM y CRNN¶

In [318]:
from IPython.display import display, HTML

html_tables_92 = f"""
<style>
    .main-container {{
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
    }}
    .table-wrapper {{
        display: inline-block;
        font-size: 14px;
        vertical-align: top;
        margin-right: 20px;
    }}
    .table-wrapper table {{
        font-size: 14px;
    }}
    .table-wrapper th, .table-wrapper td {{
        width: 100px;
        text-align: center;
    }}
    .table-wrapper h3 {{
        text-align: center;
        font-size: 18px;
        color: blue;
    }}
    h2 {{
        text-align: center;
    }}
</style>
<h2>Paciente 92</h2>
<div class="main-container">
    <div class="table-wrapper">
        <h3>LSTM</h3>
        {LSTM_92}
    </div>
    <div class="table-wrapper">
        <h3>CRNN</h3>
        {CRNN_92}
    </div>
</div>
"""


display(HTML(html_tables_92))

Paciente 92

LSTM

Evaluación+Métrica Valor mínimo Características de entrada LSTM
Entrenamiento MAE 2.600575 Measurement + Hypo_Hyper 2
Validación MAE 1.651261 Measurement + Hypo_Hyper 2
Test MAE 1.748674 Measurement + Hypo_Hyper 2
Entrenamiento RMSE 6.021919 Measurement + Hypo_Hyper 2
Validación RMSE 1.708592 Measurement + Hypo_Hyper 2
Test RMSE 2.037663 Measurement + Hypo_Hyper 2

CRNN

Evaluación+Métrica Valor mínimo Características de entrada CRNN
Entrenamiento MAE 22.130482 Measurement + Hypo_Hyper 3
Validación MAE 16.520313 Measurement + Hypo_Hyper 3
Test MAE 9.488500 Measurement + Hypo_Hyper 3
Entrenamiento RMSE 28.031647 Measurement + Hypo_Hyper 3
Validación RMSE 24.606991 Measurement + Hypo_Hyper 3
Test RMSE 15.674383 Measurement + Hypo_Hyper 3

Mejor Modelo (Paciente 92)¶

In [319]:
# Crear un nuevo DataFrame para almacenar los resultados
result = pd.DataFrame(columns=['Evaluación+Métrica', 'Valor mínimo', 'Características de entrada', 'Arquitectura', 'Configuración Hiperparámetros'])

# Definir las filas para las que quieres encontrar el valor mínimo global
rows = ['<b>Entrenamiento MAE</b>', '<b>Validación MAE</b>', '<b>Test MAE</b>', '<b>Entrenamiento RMSE</b>', '<b>Validación RMSE</b>', '<b>Test RMSE</b>']

# Diccionarios para almacenar los nombres de las tablas
tables_arch1 = {'tabla_1_CRNN_92': 1, 'tabla_2_CRNN_92': 2, 'tabla_3_CRNN_92': 3}
tables_arch2 = {'tabla_1_LSTM_92': 1, 'tabla_2_LSTM_92': 2, 'tabla_3_LSTM_92': 3}

# Para cada fila
for row in rows:
    min_vals = []
    
    # Arquitectura 1
    for table_name, arch in tables_arch1.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'CRNN', arch))

    # Arquitectura 2
    for table_name, arch in tables_arch2.items():
        min_val = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Valor mínimo'].min()
        if not globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].empty:
            min_col = globals()[table_name].loc[globals()[table_name]['Evaluación+Métrica'] == row, 'Características de entrada'].iloc[0]
        else:
            min_col = None
        min_vals.append((min_val, min_col, 'LSTM', arch))

    # Encontrar el valor mínimo global y la columna y arquitectura correspondientes
    min_val, min_col, min_arch, min_num = min(min_vals, key=lambda x: x[0])

    # Quitar las etiquetas <b> y </b> de la variable row
    row_without_tags = row.replace('<b>', '').replace('</b>', '')

    # Agregar una fila al DataFrame result usando pandas.concat
    result = pd.concat([result, pd.DataFrame({
        'Evaluación+Métrica': [row_without_tags],
        'Valor mínimo': [min_val],
        'Características de entrada': [min_col],
        'Arquitectura': [min_arch],
        'Configuración Hiperparámetros': [min_num]
    })], ignore_index=True)

# Define una función para aplicar el estilo negrita a las filas especificadas
def bold_rows(x):
    if x in ['Entrenamiento MAE', 'Validación MAE', 'Test MAE', 'Entrenamiento RMSE', 'Validación RMSE', 'Test RMSE']:
        return 'font-weight: bold'
    return ''

# Aplica el CSS y muestra el DataFrame
html_text = "<br><center><span style='font-size:26px; font-weight:bold;'>MEJOR ARQUITECTURA (Paciente 92)</span></center>"
display(HTML(html_text))

# Aplica el estilo personalizado al DataFrame resultante
styled_result = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_result = styled_result.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Mostrar el DataFrame resultante con estilo y sin índices
final_output = styled_result.hide(axis='index').to_html()

# Utilizando la función `display(HTML())` para mostrar el contenido HTML en un div centrado en la página.
display(HTML("<div style='margin: 0 auto; width:70%'>" + final_output + "</div>"))

# Nombrando
styled_result5 = result.style.applymap(bold_rows, subset=['Evaluación+Métrica'])
styled_result5 = styled_result5.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

# Muestra la predicción del nivel de glucosa en los próximos 15 minutos
# Crea un HTML con el título y los datos

# Aplica el estilo personalizado para centrar los valores de las columnas
styled_table = future_predictions_hypo_hype_2_LSTM_92.style.set_table_styles([{
    'selector': 'th, td',
    'props': [('text-align', 'center')]
}])

html = """
<div style='text-align: center;'>
<h2>Predicción del nivel de glucosa en los próximos 15 minutos</h2>
<div style='margin: 20px auto 0 40%; width:40%;'>
""" + styled_table.to_html() + "</div></div>"

# Muestra el HTML
display(HTML(html))

# Centrar la figura y la tabla utilizando CSS y HTML
display(HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
<h2>Precisión del modelo con Clarke Error Grid</h2>
"""))


# Mostrar la figura
display(fig_hypo_hype_2_LSTM_92)

# Centrar el contenido de la tabla utilizando CSS y ocultar los índices
styled_table = zone_df_hypo_hype_2_LSTM_92.style.set_properties(**{'text-align': 'center'}).hide(axis='index')

# Convertir la tabla en un objeto HTML
table_html = f"<center>{styled_table.to_html()}</center>"

# Mostrar la tabla centrada
display(HTML(table_html))

MEJOR ARQUITECTURA (Paciente 92)
Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 2.600575 Measurement + Hypo_Hyper LSTM 2
Validación MAE 1.651261 Measurement + Hypo_Hyper LSTM 2
Test MAE 1.748674 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 6.021919 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.708592 Measurement + Hypo_Hyper LSTM 2
Test RMSE 2.037663 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-01-10 16:15:00 186.475082

Precisión del modelo con Clarke Error Grid

Zona Conteo Proporción
A 12607 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Conclusión Paciente 92¶

Los resultados nos aclara que la mejor opción para el paciente 92 es utilizar una arquitectura LSTM con una configuración de hiperparámetros 2.

Arquitectura LSTM con la LSTM_2 global:

  • seq_length: 8. Longitud de las secuencias de entrada al modelo LSTM.
  • patience: 3. Número de épocas sin mejora en la pérdida de validación antes de detener el entrenamiento.
  • batch_size: 64. Tamaño del lote para el entrenamiento del modelo.
  • units: 64. Número de neuronas en la capa LSTM.
  • activation: 'relu'. Función de activación utilizada en la capa LSTM.
  • Dense: 1. Capa de neuronas completamente conectadas, en este caso con 1 neurona de salida.
  • optimizer: 'adam'. Optimizador utilizado para actualizar los pesos del modelo durante el entrenamiento.
  • loss: ‘mean_absolute_error’. Función de pérdida utilizada para calcular el error durante el entrenamiento.
  • epochs: 20. Número máximo de épocas para entrenar el modelo.

La conclusión final para el paciente 92 es utilizar la arquitectura LSTM con la configuración de hiperparámetros y las características de entrada Measurement + Hypo_Hyper para la predicción de sus niveles de glucosa para los próximos 15 minutos ya que nos arrojan unos resultados muy buenos.

4. Evaluación de los 5 pacientes con mejores arquitecturas¶

In [515]:
# Definir los títulos
titles = ["MEJOR ARQUITECTURA (Paciente 22)", "MEJOR ARQUITECTURA (Paciente 24)", "MEJOR ARQUITECTURA (Paciente 11)", 
          "MEJOR ARQUITECTURA (Paciente 83)", "MEJOR ARQUITECTURA (Paciente 92)"]

# Lista de DataFrames para las predicciones futuras de los pacientes
future_predictions = [future_predictions_hypo_hype_2_LSTM_22, future_predictions_hypo_hype_2_LSTM_24, future_predictions_hypo_hype_2_LSTM_11, 
                      future_predictions_hypo_hype_2_LSTM_83, future_predictions_hypo_hype_2_LSTM_92]

# Lista de los resultados
results = [styled_result1, styled_result2, styled_result3, styled_result4, styled_result5]

# Lista de figuras y tablas zone_dfs para cada paciente
figures = [fig_hypo_hype_2_LSTM_22, fig_hypo_hype_2_LSTM_24, fig_hypo_hype_2_LSTM_11, fig_hypo_hype_2_LSTM_83, fig_hypo_hype_2_LSTM_92]
zone_dfs = [zone_df_hypo_hype_2_LSTM_22, zone_df_hypo_hype_2_LSTM_24, zone_df_hypo_hype_2_LSTM_11, zone_df_hypo_hype_2_LSTM_83, zone_df_hypo_hype_2_LSTM_92]

# Lista para guardar las tablas HTML
tables = []

# Bucle para crear y formatear las tablas
for i in range(5):
    # Aplicar el estilo personalizado al DataFrame para centrar las columnas específicas
    styled_df = future_predictions[i].style.set_table_styles([
        {'selector': '.col0, .col1', 'props': [('text-align', 'center')]}
    ])
    html_table = results[i].hide(axis='index').to_html().replace("<table", "<table style='margin: 0 auto;'") + \
    f"""
    <div style='text-align: center;'>
    <h3>Predicción del nivel de glucosa en los próximos 15 minutos</h3>
    """ + styled_df.to_html().replace("<table", "<table style='margin: 0 auto;'") + "</div><br>"
    # Generar un div para cada tabla con la clase 'table-container' y añadir los títulos
    html_table = f"<div class='table-container'><h2>{titles[i]}</h2>{html_table}</div>"
    # Añadir la tabla a la lista de tablas
    tables.append(html_table)

# Bucle para mostrar las tablas, títulos y gráficas de cada paciente
for i in range(5):
    # Mostrar la tabla
    display(HTML(tables[i]))
    
    # Mostrar la gráfica
    display(figures[i])
    
    # Aplicar el estilo personalizado al DataFrame para centrar las columnas
    styled_df = zone_dfs[i].style.set_table_styles([
        {'selector': '.col0, .col1', 'props': [('text-align', 'center')]}
    ])
    html_table_zone_dfs = styled_df.hide(axis='index').to_html().replace("<table", "<table style='margin: 0 auto;'") + "<br>"
    
    # Mostrar la tabla zone_dfs
    display(HTML(html_table_zone_dfs))

MEJOR ARQUITECTURA (Paciente 22)

Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.388647 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.293932 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.237700 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.244411 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-16 00:01:00 157.521851

Zona Conteo Proporción
A 11758 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

MEJOR ARQUITECTURA (Paciente 24)

Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.348582 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.489993 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.520977 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 4.971605 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 0.733889 Measurement + Hypo_Hyper LSTM 2
Test RMSE 0.772963 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-12 08:45:00 158.111694

Zona Conteo Proporción
A 12836 100.00%
B 0 0.00%
C 0 0.00%
D 0 0.00%
E 0 0.00%

MEJOR ARQUITECTURA (Paciente 11)

Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.665202 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.832716 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.822650 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 5.385064 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.542349 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.477171 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-02-25 19:30:00 169.183929

Zona Conteo Proporción
A 12989 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

MEJOR ARQUITECTURA (Paciente 83)

Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.297980 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.890543 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.764750 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 3.966413 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.913211 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.413944 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-14 11:00:00 160.750839

Zona Conteo Proporción
A 14216 100.00%
B 0 0.00%
C 0 0.00%
D 0 0.00%
E 0 0.00%

MEJOR ARQUITECTURA (Paciente 92)

Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 2.600575 Measurement + Hypo_Hyper LSTM 2
Validación MAE 1.651261 Measurement + Hypo_Hyper LSTM 2
Test MAE 1.748674 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 6.021919 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.708592 Measurement + Hypo_Hyper LSTM 2
Test RMSE 2.037663 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-01-10 16:15:00 186.475082

Zona Conteo Proporción
A 12607 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

Arquitectura con mayor rendimiento¶

In [516]:
# Dataframes originales
df1 = styled_result1.data.copy()
df2 = styled_result2.data.copy()
df3 = styled_result3.data.copy()
df4 = styled_result4.data.copy()
df5 = styled_result5.data.copy()

# Obtener los valores mínimos de cada tabla
min_val1 = df1['Valor mínimo'].min()
min_val2 = df2['Valor mínimo'].min()
min_val3 = df3['Valor mínimo'].min()
min_val4 = df4['Valor mínimo'].min()
min_val5 = df5['Valor mínimo'].min()

# Identificar qué tabla tiene el valor mínimo más bajo
min_table_val = min(min_val1, min_val2, min_val3, min_val4, min_val5)

if min_table_val == min_val1:
    min_table_name = "MEJOR ARQUITECTURA (Paciente 22)"
    min_table = df1
    future_prediction = future_predictions_hypo_hype_2_LSTM_22
    result = styled_result1
    figure = fig_hypo_hype_2_LSTM_22
    zone_df = zone_df_hypo_hype_2_LSTM_22
elif min_table_val == min_val2:
    min_table_name = "MEJOR ARQUITECTURA (Paciente 24)"
    min_table = df2
    future_prediction = future_predictions_hypo_hype_2_LSTM_24
    result = styled_result2
    figure = fig_hypo_hype_2_LSTM_24
    zone_df = zone_df_hypo_hype_2_LSTM_24
elif min_table_val == min_val3:
    min_table_name = "MEJOR ARQUITECTURA (Paciente 11)"
    min_table = df3
    future_prediction = future_predictions_hypo_hype_2_LSTM_11
    result = styled_result3
    figure = fig_hypo_hype_2_LSTM_11
    zone_df = zone_df_hypo_hype_2_LSTM_11
elif min_table_val == min_val4:
    min_table_name = "MEJOR ARQUITECTURA (Paciente 83)"
    min_table = df4
    future_prediction = future_predictions_hypo_hype_2_LSTM_83
    result = styled_result4
    figure = fig_hypo_hype_2_LSTM_83
    zone_df = zone_df_hypo_hype_2_LSTM_83
else:
    min_table_name = "MEJOR ARQUITECTURA (Paciente 92)"
    min_table = df5
    future_prediction = future_predictions_hypo_hype_2_LSTM_92
    result = styled_result5
    figure = fig_hypo_hype_2_LSTM_92
    zone_df = zone_df_hypo_hype_2_LSTM_92

print(f"La tabla con el valor mínimo más bajo es: {min_table_name}")

# Definir el título para la tabla con el valor mínimo más bajo como Mejor Arquitectura (Paciente X)
min_title = f"{min_table_name}"

# Aplicar el estilo personalizado al DataFrame para centrar las columnas específicas
styled_df = future_prediction.style.set_table_styles([
        {'selector': '.col0, .col1', 'props': [('text-align', 'center')]}
])
html_table = result.hide(axis='index').to_html().replace("<table", "<table style='margin: 0 auto;'") + \
f"""
<div style='text-align: center;'>
<h3>Predicción del nivel de glucosa en los próximos 15 minutos</h3>
""" + styled_df.to_html().replace("<table", "<table style='margin: 0 auto;'") + "</div><br>"
# Generar un div para cada tabla con la clase 'table-container' y añadir los títulos y el título adicional centrado y subrayado.
html_table = f"<div class='table-container'><h1 style='text-align: center; text-decoration: underline;'>ARQUITECTURA CON MAYOR RENDIMIENTO</h1><h2>{min_title}</h2>{html_table}</div>"

# Mostrar la tabla y la gráfica del paciente con el valor mínimo más bajo

# Mostrar la tabla de resultados y predicciones futuras del paciente con el valor mínimo más bajo.
display(HTML(html_table))

# Mostrar la gráfica del paciente con el valor mínimo más bajo.
display(figure)

# Aplicar el estilo personalizado al DataFrame para centrar las columnas.
styled_df_zone_dfs_min_patient=zone_df.style.set_table_styles([
        {'selector': '.col0, .col1', 'props': [('text-align', 'center')]}
])
html_table_zone_dfs_min_patient=styled_df_zone_dfs_min_patient.hide(axis='index').to_html().replace("<table", "<table style='margin: 0 auto;'") + "<br>"

# Mostrar la tabla zone_dfs del paciente con el valor mínimo más bajo.
display(HTML(html_table_zone_dfs_min_patient))
La tabla con el valor mínimo más bajo es: MEJOR ARQUITECTURA (Paciente 22)

ARQUITECTURA CON MAYOR RENDIMIENTO

MEJOR ARQUITECTURA (Paciente 22)

Evaluación+Métrica Valor mínimo Características de entrada Arquitectura Configuración Hiperparámetros
Entrenamiento MAE 1.235351 Measurement + Hypo_Hyper LSTM 2
Validación MAE 0.388647 Measurement + Hypo_Hyper LSTM 2
Test MAE 0.293932 Measurement + Hypo_Hyper LSTM 2
Entrenamiento RMSE 4.759490 Measurement + Hypo_Hyper LSTM 2
Validación RMSE 1.237700 Measurement + Hypo_Hyper LSTM 2
Test RMSE 1.244411 Measurement + Hypo_Hyper LSTM 2

Predicción del nivel de glucosa en los próximos 15 minutos

  Datos predichos
date  
2022-03-16 00:01:00 157.521851

Zona Conteo Proporción
A 11758 99.99%
B 1 0.01%
C 0 0.00%
D 0 0.00%
E 0 0.00%

La tabla inferior, muestra la distribución final de los conjuntos de datos de entrenamiento, validación y prueba para los pacientes 22, 24, 11, 83 y 92, después de aplicar la estrategia de ventana deslizante (8 secuencias con 1 stride) y la eliminación de las secuencias incompletas (27 registros en los tres conjuntos). Por lo tanto, estos valores representan los datos efectivamente utilizados para el modelado, donde el 80% se destina al conjunto de entrenamiento, y el 10% restante se divide equitativamente entre los conjuntos de validación y prueba.

image.png

Evaluación y Conclusión¶

El análisis llevado a cabo en este estudio refleja una superioridad de la arquitectura LSTM con hiperparámetros 2 y las características de entrada 'Measurement + Hypo_Hyper' en la predicción de los niveles de glucosa para los próximos 15 minutos. Esta evaluación ha surgido de la comparación exhaustiva entre diferentes configuraciones de hiperparámetros y algoritmos, incluyendo LSTM y CRNN, y una variedad de características de entrada. Esta superioridad se mantiene constante independientemente de las particularidades de los datos de entrada. La evaluación se ha aplicado a cinco pacientes seleccionados (pacientes 24, 11, 22, 83 y 92) de un conjunto de 110, generando pronósticos altamente precisos de los niveles de glucosa a corto plazo. En cada caso, los valores de predicción a 15 minutos han demostrado un mínimo Error de raíz cuadrada media (RMSE), lo que refleja la capacidad del modelo LSTM para proporcionar predicciones precisas para datos no vistos previamente.

Evaluación de los resultados finales de los 5 pacientes:

  • La arquitectura del paciente 22 con las características de entrada Measurement + Hypo_Hyper ofrecen una predicción para los próximos 15 minutos de nivel de glucosa de 157.521851. Estos valores son muy precisos dado que el resultado del Test RMSE arroja un valor mínimo de 1.244411. Esto nos asegura tener un control muy alto y con mucha precisión a datos no conocidos como son los próximos 15 minutos.
  • La arquitectura del paciente 24 con las características de entrada Measurement + Hypo_Hyper ofrecen una predicción para los próximos 15 minutos de nivel de glucosa de 158.111694. Estos valores son muy precisos dado que el resultado del Test RMSE arroja un valor mínimo de 0.772963. Esto nos asegura tener un control muy alto y con mucha precisión a datos no conocidos como son los próximos 15 minutos.
  • La arquitectura del paciente 11 con las características de entrada Measurement + Hypo_Hyper ofrecen una predicción para los próximos 15 minutos de nivel de glucosa de 169.183929. Estos valores son muy precisos dado que el resultado del Test RMSE arroja un valor mínimo de 1.477171. Esto nos asegura tener un control muy alto y con mucha precisión a datos no conocidos como son los próximos 15 minutos.
  • La arquitectura del paciente 83 con las características de entrada Measurement + Hypo_Hyper ofrecen una predicción para los próximos 15 minutos de nivel de glucosa de 160.750839. Estos valores son muy precisos dado que el resultado del Test RMSE arroja un valor mínimo de 1.413944. Esto nos asegura tener un control muy alto y con mucha precisión a datos no conocidos como son los próximos 15 minutos.
  • La arquitectura del paciente 92 con las características de entrada Measurement + Hypo_Hyper ofrecen una predicción para los próximos 15 minutos de nivel de glucosa de 186.475082. Estos valores son muy precisos dado que el resultado del RMSE Test arroja un valor mínimo de 2.037663. Esto nos asegura tener un control muy alto y con mucha precisión a datos no conocidos como son los próximos 15 minutos.

El hecho de que estos resultados sean consistentes y altamente precisos en todos los pacientes analizados proporciona una sólida verificación de la eficacia del enfoque propuesto. Por ende, podemos afirmar con confianza que el LSTM con la configuración de hiperparámetros 2 y las características de entrada 'Measurement + Hypo_Hyper' es un modelo efectivo para la predicción de los niveles de glucosa.

Es importante resaltar que el modelo LSTM exhibió una alta precisión tanto en datos de entrenamiento como en datos de prueba. Esta característica indica que el modelo está bien ajustado, sin signos de sobreajuste (overfitting) o subajuste (underfitting), lo que sugiere que tiene la capacidad de generalizar bien a datos no vistos. Esta es una cualidad esencial que refuerza la fiabilidad del modelo LSTM para la predicción de los niveles de glucosa en pacientes con diabetes.

En conclusión, los resultados obtenidos en esta investigación subrayan la eficacia de las Redes Neuronales Recurrentes de Memoria a Largo Plazo (LSTM) para predecir los niveles de glucosa en pacientes con diabetes. El alto rendimiento del modelo LSTM, confirmado a través de la comparación con la arquitectura CRNN y la variedad de características de entrada, sugiere que tiene un gran potencial para ser utilizado como una herramienta precisa y confiable en la gestión futura de la diabetes.